Sleazy Fork is available in English.

FC2PPVDB Enhanced

FC2/JAV 资源站增强脚本:磁力链接自动聚合、高清封面补全、图集预览、历史记录同步与过滤。支持 FD2PPV, Supjav, JavDB, MissAV, JavFC2, FC2DB, FC2CMADB 等站点。

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name            FC2PPVDB Enhanced
// @name:en         FC2PPVDB Enhanced
// @namespace       https://greasyfork.org/zh-CN/scripts/552583-fc2ppvdb-enhanced
// @version         2.1.5
// @author          Icarusle
// @description     FC2/JAV 资源站增强脚本:磁力链接自动聚合、高清封面补全、图集预览、历史记录同步与过滤。支持 FD2PPV, Supjav, JavDB, MissAV, JavFC2, FC2DB, FC2CMADB 等站点。
// @description:en  Enhancement script for FC2/JAV sites: magnet aggregation, HD cover restoration, gallery preview, history sync and filtering. Supports FD2PPV, Supjav, JavDB, MissAV, JavFC2, FC2DB, FC2CMADB, etc.
// @license         MIT
// @icon            https://fc2ppvdb.com/favicon.ico
// @match           https://fd2ppv.cc/*
// @match           https://supjav.com/*
// @match           https://missav.ws/*
// @match           https://missav.ai/*
// @match           https://javdb.com/*
// @match           https://fc2db.net/*
// @match           https://javfc2.xyz/*
// @match           https://fc2cmadb.com/*
// @require         https://cdn.jsdelivr.net/npm/[email protected]/dist/dexie.min.js
// @connect         fd2ppv.cc
// @connect         fourhoi.com
// @connect         paipancon.com
// @connect         sukebei.nyaa.si
// @connect         supabase.co
// @grant           GM_addStyle
// @grant           GM_addValueChangeListener
// @grant           GM_deleteValue
// @grant           GM_getValue
// @grant           GM_info
// @grant           GM_notification
// @grant           GM_openInTab
// @grant           GM_registerMenuCommand
// @grant           GM_removeValueChangeListener
// @grant           GM_setClipboard
// @grant           GM_setValue
// @grant           GM_unregisterMenuCommand
// @grant           GM_xmlhttpRequest
// @grant           unsafeWindow
// @run-at          document-end
// @noframes
// ==/UserScript==

!function(){function e(e,t){return(t||"")+" (SystemJS Error#"+e+" https://github.com/systemjs/systemjs/blob/main/docs/errors.md#"+e+")"}function t(e,t){if(-1!==e.indexOf("\\")&&(e=e.replace(j,"/")),"/"===e[0]&&"/"===e[1])return t.slice(0,t.indexOf(":")+1)+e;if("."===e[0]&&("/"===e[1]||"."===e[1]&&("/"===e[2]||2===e.length&&(e+="/"))||1===e.length&&(e+="/"))||"/"===e[0]){var n,r=t.slice(0,t.indexOf(":")+1);if(n="/"===t[r.length+1]?"file:"!==r?(n=t.slice(r.length+2)).slice(n.indexOf("/")+1):t.slice(8):t.slice(r.length+("/"===t[r.length])),"/"===e[0])return t.slice(0,t.length-n.length-1)+e;for(var i=n.slice(0,n.lastIndexOf("/")+1)+e,o=[],s=-1,u=0;u<i.length;u++)-1!==s?"/"===i[u]&&(o.push(i.slice(s,u+1)),s=-1):"."===i[u]?"."!==i[u+1]||"/"!==i[u+2]&&u+2!==i.length?"/"===i[u+1]||u+1===i.length?u+=1:s=u:(o.pop(),u+=2):s=u;return-1!==s&&o.push(i.slice(s)),t.slice(0,t.length-n.length)+o.join("")}}function n(e,n){return t(e,n)||(-1!==e.indexOf(":")?e:t("./"+e,n))}function r(e,n,r,i,o){for(var s in e){var a=t(s,r)||s,f=e[s];if("string"==typeof f){var l=c(i,t(f,r)||f,o);l?n[a]=l:u("W1",s,f,"bare specifier did not resolve")}}}function i(e,t,i){var o;for(o in e.imports&&r(e.imports,i.imports,t,i,null),e.scopes||{}){var s=n(o,t);r(e.scopes[o],i.scopes[s]||(i.scopes[s]={}),t,i,s)}for(o in e.depcache||{})i.depcache[n(o,t)]=e.depcache[o];for(o in e.integrity||{})i.integrity[n(o,t)]=e.integrity[o]}function o(e,t){if(t[e])return e;var n=e.length;do{var r=e.slice(0,n+1);if(r in t)return r}while(-1!==(n=e.lastIndexOf("/",n-1)))}function s(e,t){var n=o(e,t);if(n){var r=t[n];if(null===r)return;if(!(e.length>n.length&&"/"!==r[r.length-1]))return r+e.slice(n.length);u("W2",n,r,"should have a trailing '/'")}}function u(t,n,r,i){console.warn(e(t,"Package target "+i+", resolving target '"+r+"' for "+n))}function c(e,t,n){for(var r=e.scopes,i=n&&o(n,r);i;){var u=s(t,r[i]);if(u)return u;i=o(i.slice(0,i.lastIndexOf("/")),r)}return s(t,e.imports)||-1!==t.indexOf(":")&&t}function a(){this[M]={}}function f(e){return e.id}function l(e,t,n,r){if(e.onload(n,t.id,t.d&&t.d.map(f),!!r),n)throw n}function d(t,n,r,i){var o=t[M][n];if(o)return o;var s=[],u=Object.create(null);P&&Object.defineProperty(u,P,{value:"Module"});var c=Promise.resolve().then((function(){return t.instantiate(n,r,i)})).then((function(r){if(!r)throw Error(e(2,"Module "+n+" did not instantiate"));var i=r[1]((function(e,t){o.h=!0;var n=!1;if("string"==typeof e)e in u&&u[e]===t||(u[e]=t,n=!0);else{for(var r in e)t=e[r],r in u&&u[r]===t||(u[r]=t,n=!0);e&&e.__esModule&&(u.__esModule=e.__esModule)}if(n)for(var i=0;i<s.length;i++){var c=s[i];c&&c(u)}return t}),2===r[1].length?{import:function(e,r){return t.import(e,n,r)},meta:t.createContext(n)}:void 0);return o.e=i.execute||function(){},[r[0],i.setters||[],r[2]||[]]}),(function(e){throw o.e=null,o.er=e,l(t,o,e,!0),e})),a=c.then((function(e){return Promise.all(e[0].map((function(r,i){var o=e[1][i],s=e[2][i];return Promise.resolve(t.resolve(r,n)).then((function(e){var r=d(t,e,n,s);return Promise.resolve(r.I).then((function(){return o&&(r.i.push(o),!r.h&&r.I||o(r.n)),r}))}))}))).then((function(e){o.d=e}))}));return o=t[M][n]={id:n,i:s,n:u,m:i,I:c,L:a,h:!1,d:void 0,e:void 0,er:void 0,E:void 0,C:void 0,p:void 0}}function h(e,t,n,r){if(!r[t.id])return r[t.id]=!0,Promise.resolve(t.L).then((function(){return t.p&&null!==t.p.e||(t.p=n),Promise.all(t.d.map((function(t){return h(e,t,n,r)})))})).catch((function(n){if(t.er)throw n;throw t.e=null,l(e,t,n,!1),n}))}function p(e,t){return t.C=h(e,t,t,{}).then((function(){return v(e,t,{})})).then((function(){return t.n}))}function v(e,t,n){function r(){try{var n=o.call(L);if(n)return n=n.then((function(){t.C=t.n,t.E=null,l(e,t,null,!0)}),(function(n){throw t.er=n,t.E=null,l(e,t,n,!0),n})),t.E=n;t.C=t.n,t.L=t.I=void 0}catch(r){throw t.er=r,r}finally{l(e,t,t.er,!0)}}if(!n[t.id]){if(n[t.id]=!0,!t.e){if(t.er)throw t.er;return t.E?t.E:void 0}var i,o=t.e;return t.e=null,t.d.forEach((function(r){try{var o=v(e,r,n);o&&(i=i||[]).push(o)}catch(s){throw t.er=s,l(e,t,s,!1),s}})),i?Promise.all(i).then(r):r()}}function m(){[].forEach.call(document.querySelectorAll("script"),(function(t){if(!t.sp)if("systemjs-module"===t.type){if(t.sp=!0,!t.src)return;System.import("import:"===t.src.slice(0,7)?t.src.slice(7):n(t.src,g)).catch((function(e){if(e.message.indexOf("https://github.com/systemjs/systemjs/blob/main/docs/errors.md#3")>-1){var n=document.createEvent("Event");n.initEvent("error",!1,!1),t.dispatchEvent(n)}return Promise.reject(e)}))}else if("systemjs-importmap"===t.type){t.sp=!0;var r=t.src?(System.fetch||fetch)(t.src,{integrity:t.integrity,priority:t.fetchPriority,passThrough:!0}).then((function(e){if(!e.ok)throw Error("Invalid status code: "+e.status);return e.text()})).catch((function(n){return n.message=e("W4","Error fetching systemjs-import map "+t.src)+"\n"+n.message,console.warn(n),"function"==typeof t.onerror&&t.onerror(),"{}"})):t.innerHTML;W=W.then((function(){return r})).then((function(n){!function(t,n,r){var o={};try{o=JSON.parse(n)}catch(s){console.warn(Error(e("W5","systemjs-importmap contains invalid JSON")+"\n\n"+n+"\n"))}i(o,r,t)}(N,n,t.src||g)}))}}))}var g,y="undefined"!=typeof Symbol,b="undefined"!=typeof self,S="undefined"!=typeof document,w=b?self:global;if(S){var O=document.querySelector("base[href]");O&&(g=O.href)}if(!g&&"undefined"!=typeof location){var E=(g=location.href.split("#")[0].split("?")[0]).lastIndexOf("/");-1!==E&&(g=g.slice(0,E+1))}var x,j=/\\/g,P=y&&Symbol.toStringTag,M=y?Symbol():"@",I=a.prototype;I.import=function(e,t,n){var r=this;return t&&"object"==typeof t&&(n=t,t=void 0),Promise.resolve(r.prepareImport()).then((function(){return r.resolve(e,t,n)})).then((function(e){var t=d(r,e,void 0,n);return t.C||p(r,t)}))},I.createContext=function(e){var t=this;return{url:e,resolve:function(n,r){return Promise.resolve(t.resolve(n,r||e))}}},I.onload=function(){},I.register=function(e,t,n){x=[e,t,n]},I.getRegister=function(){var e=x;return x=void 0,e};var L=Object.freeze(Object.create(null));w.System=new a;var C,R,W=Promise.resolve(),N={imports:{},scopes:{},depcache:{},integrity:{}},T=S;if(I.prepareImport=function(e){return(T||e)&&(m(),T=!1),W},I.getImportMap=function(){return JSON.parse(JSON.stringify(N))},S&&(m(),window.addEventListener("DOMContentLoaded",m)),I.addImportMap=function(e,t){i(e,t||g,N)},S){window.addEventListener("error",(function(e){J=e.filename,_=e.error}));var A=location.origin}I.createScript=function(e){var t=document.createElement("script");t.async=!0,e.indexOf(A+"/")&&(t.crossOrigin="anonymous");var n=N.integrity[e];return n&&(t.integrity=n),t.src=e,t};var J,_,k={},U=I.register;I.register=function(e,t){if(S&&"loading"===document.readyState&&"string"!=typeof e){var n=document.querySelectorAll("script[src]"),r=n[n.length-1];if(r){C=e;var i=this;R=setTimeout((function(){k[r.src]=[e,t],i.import(r.src)}))}}else C=void 0;return U.call(this,e,t)},I.instantiate=function(t,n){var r=k[t];if(r)return delete k[t],r;var i=this;return Promise.resolve(I.createScript(t)).then((function(r){return new Promise((function(o,s){r.addEventListener("error",(function(){s(Error(e(3,"Error loading "+t+(n?" from "+n:""))))})),r.addEventListener("load",(function(){if(document.head.removeChild(r),J===t)s(_);else{var e=i.getRegister(t);e&&e[0]===C&&clearTimeout(R),o(e)}})),document.head.appendChild(r)}))}))},I.shouldFetch=function(){return!1},"undefined"!=typeof fetch&&(I.fetch=fetch);var $=I.instantiate,B=/^(text|application)\/(x-)?javascript(;|$)/;I.instantiate=function(t,n,r){var i=this;return this.shouldFetch(t,n,r)?this.fetch(t,{credentials:"same-origin",integrity:N.integrity[t],meta:r}).then((function(r){if(!r.ok)throw Error(e(7,r.status+" "+r.statusText+", loading "+t+(n?" from "+n:"")));var o=r.headers.get("content-type");if(!o||!B.test(o))throw Error(e(4,'Unknown Content-Type "'+o+'", loading '+t+(n?" from "+n:"")));return r.text().then((function(e){return e.indexOf("//# sourceURL=")<0&&(e+="\n//# sourceURL="+t),(0,eval)(e),i.getRegister(t)}))})):$.apply(this,arguments)},I.resolve=function(n,r){return c(N,t(n,r=r||g)||n,r)||function(t,n){throw Error(e(8,"Unable to resolve bare specifier '"+t+(n?"' from "+n:"'")))}(n,r)};var F=I.instantiate;I.instantiate=function(e,t,n){var r=N.depcache[e];if(r)for(var i=0;i<r.length;i++)d(this,this.resolve(r[i],e),e);return F.call(this,e,t,n)},b&&"function"==typeof importScripts&&(I.instantiate=function(e){var t=this;return Promise.resolve().then((function(){return importScripts(e),t.getRegister(e)}))}),function(e){function t(t){return!e.hasOwnProperty(t)||!isNaN(t)&&t<e.length||a&&e[t]&&"undefined"!=typeof window&&e[t].parent===window}var n,r,i,o=e.System.constructor.prototype,s=o.import;o.import=function(o,u,c){return function(){for(var o in n=r=void 0,e)t(o)||(n?r||(r=o):n=o,i=o)}(),s.call(this,o,u,c)};var u=[[],function(){return{}}],c=o.getRegister;o.getRegister=function(){var o=c.call(this);if(o)return o;var s,a=function(o){var s,u,c=0;for(var a in e)if(!t(a)){if(0===c&&a!==n||1===c&&a!==r)return a;s?(i=a,u=o&&u||a):s=a===i,c++}return u}(this.firstGlobalProp);if(!a)return u;try{s=e[a]}catch(f){return u}return[[],function(e){return{execute:function(){e(s),e({default:s,__useDefault:!0})}}}]};var a="undefined"!=typeof navigator&&-1!==navigator.userAgent.indexOf("Trident")}("undefined"!=typeof self?self:global),function(e){var t=e.System.constructor.prototype,r=/^[^#?]+\.(css|html|json|wasm)([?#].*)?$/,i=t.shouldFetch.bind(t);t.shouldFetch=function(e){return i(e)||r.test(e)};var o=/^application\/json(;|$)/,s=/^text\/css(;|$)/,u=/^application\/wasm(;|$)/,c=t.fetch;t.fetch=function(t,r){return c(t,r).then((function(i){if(r.passThrough)return i;if(!i.ok)return i;var c=i.headers.get("content-type");return o.test(c)?i.json().then((function(e){return new Response(new Blob(['System.register([],function(e){return{execute:function(){e("default",'+JSON.stringify(e)+")}}})"],{type:"application/javascript"}))})):s.test(c)?i.text().then((function(e){return e=e.replace(/url\(\s*(?:(["'])((?:\\.|[^\n\\"'])+)\1|((?:\\.|[^\s,"'()\\])+))\s*\)/g,(function(e,r,i,o){return["url(",r,n(i||o,t),r,")"].join("")})),new Response(new Blob(["System.register([],function(e){return{execute:function(){var s=new CSSStyleSheet();s.replaceSync("+JSON.stringify(e)+');e("default",s)}}})'],{type:"application/javascript"}))})):u.test(c)?(WebAssembly.compileStreaming?WebAssembly.compileStreaming(i):i.arrayBuffer().then(WebAssembly.compile)).then((function(n){e.System.wasmModules||(e.System.wasmModules=Object.create(null)),e.System.wasmModules[t]=n;var r=[],i=[];return WebAssembly.Module.imports&&WebAssembly.Module.imports(n).forEach((function(e){var t=JSON.stringify(e.module);-1===r.indexOf(t)&&(r.push(t),i.push("function(m){i["+t+"]=m}"))})),new Response(new Blob(["System.register(["+r.join(",")+"],function(e){var i={};return{setters:["+i.join(",")+"],execute:function(){return WebAssembly.instantiate(System.wasmModules["+JSON.stringify(t)+"],i).then(function(m){e(m.exports)})}}})"],{type:"application/javascript"}))})):i}))}}("undefined"!=typeof self?self:global);var q="undefined"!=typeof Symbol&&Symbol.toStringTag;I.get=function(e){var t=this[M][e];if(t&&null===t.e&&!t.E)return t.er?null:t.n},I.set=function(t,n){try{new URL(t)}catch(s){console.warn(Error(e("W3",'"'+t+'" is not a valid URL to set in the module registry')))}var r;q&&"Module"===n[q]?r=n:(r=Object.assign(Object.create(null),n),q&&Object.defineProperty(r,q,{value:"Module"}));var i=Promise.resolve(r),o=this[M][t]||(this[M][t]={id:t,i:[],h:!1,d:[],e:null,er:void 0,E:void 0});return!o.e&&!o.E&&(Object.assign(o,{n:r,I:void 0,L:void 0,C:i}),r)},I.has=function(e){return!!this[M][e]},I.delete=function(e){var t=this[M],n=t[e];if(!n||n.p&&null!==n.p.e||n.E)return!1;var r=n.i;return n.d&&n.d.forEach((function(e){var t=e.i.indexOf(n);-1!==t&&e.i.splice(t,1)})),delete t[e],function(){var n=t[e];if(!n||!r||null!==n.e||n.E)return!1;r.forEach((function(e){n.i.push(e),e(n.n)})),r=null}};var D="undefined"!=typeof Symbol&&Symbol.iterator;I.entries=function(){var e,t,n=this,r=Object.keys(n[M]),i=0,o={next:function(){for(;void 0!==(t=r[i++])&&void 0===(e=n.get(t)););return{done:void 0===t,value:void 0!==t&&[t,e]}}};return o[D]=function(){return this},o}}();
!function(t){function e(t){t.registerRegistry=Object.create(null),t.namedRegisterAliases=Object.create(null)}var r=t.System;e(r);var i,s,n=r.constructor.prototype,l=r.constructor,a=function(){l.call(this),e(this)};a.prototype=n,r.constructor=a;var o=n.register;n.register=function(t,e,r,n){if("string"!=typeof t)return o.apply(this,arguments);var l=[e,r,n];return this.registerRegistry[t]=l,i||(i=l,s=t),Promise.resolve().then((function(){i=null,s=null})),o.apply(this,[e,r,n])};var u=n.resolve;n.resolve=function(t,e){try{return u.call(this,t,e)}catch(r){if(t in this.registerRegistry)return this.namedRegisterAliases[t]||t;throw r}};var c=n.instantiate;n.instantiate=function(t,e,r){var i=this.registerRegistry[t];return i?(this.registerRegistry[t]=null,i):c.call(this,t,e,r)};var g=n.getRegister;n.getRegister=function(t){var e=g.call(this,t);s&&t&&(this.namedRegisterAliases[s]=t);var r=i||e;return i=null,s=null,r}}("undefined"!=typeof self?self:global);
;(typeof System!='undefined')&&(System=new System.constructor());
System.addImportMap({ imports: {"dexie":"user:dexie"} });
System.set("user:dexie", (()=>{const _=Dexie;('default' in _)||(_.default=_);return _})());
System.register("./___monkey.entry.js", ['dexie'],(function(exports,module){'use strict';var Dexie;return{setters:[function(module){Dexie=module.default;}],execute:(function(){exports("r",resolveService);var resolveScriptVersion = () => {
	if (typeof GM_info !== "undefined") return GM_info.script.version;
	return "2.1.5";
};
var SCRIPT_INFO = exports("k",{
	NAME: "FC2PPVDB Enhanced",
	VERSION: resolveScriptVersion(),
	GREASYFORK_URL: "https://greasyfork.org/scripts/552583"
});
var NETWORK = {
	CHUNK_SIZE: 16,
	HEALTH_CHECK_TIMEOUT: 5e3
};
var TIMING = exports("i",{
	DEBOUNCE_MS: 300,
	THROTTLE_MS: 200,
	RETRY_DELAY_MS: 1e3,
	MAGNET_BASE_DELAY_MS: 800,
	UI_TRANSITION_FAST: 150,
	UI_TRANSITION_NORMAL: 300,
	UI_TRANSITION_SLOW: 500,
	UI_ANIMATION_BADGE: 600,
	TOAST_DEFAULT_DURATION: 3e3,
	SCRAPER_TOAST_DURATION: 15e3,
	RELOAD_DELAY_FAST: 800,
	RELOAD_DELAY_NORMAL: 1500,
	SCRIPT_INJECTION_DELAY: 400,
	SYNC_DEBOUNCE_MS: 2e3,
	SYNC_INIT_DELAY: 5e3,
	SYNC_REALTIME_POLL_MS: 3e4,
	MAGNET_RANDOM_DELAY_MS: 3e3,
	CLOUDFLARE_BACKOFF_MS: 6e4,
	FD2_BACKOFF_MS: 3e5,
	RATE_LIMIT_BACKOFF_MS: 6e4,
	ACTRESS_BASE_DELAY_MS: 1500,
	ACTRESS_RANDOM_DELAY_MS: 1500,
	POLITE_DELAY_MS: 200,
	MAGNET_JITTER_MS: 3e3,
	POLL_INTERVAL_MS: 100,
	POLL_TIMEOUT_MS: 3e4,
	DOM_READY_TIMEOUT: 1e4,
	PREVIEW_ERROR_DELAY: 3e3,
	LINK_PRELOAD_TIMEOUT: 6e4,
	MAX_BACKOFF_MS: 6e5,
	PREVIEW_JITTER_MS: 500,
	HTTP_FD2_TIMEOUT: 1e4,
	SLIDESHOW_INTERVAL: 3e3,
	TITLE_MATCH_PROXIMITY: 15
});
var CLOUDFLARE_INDICATORS = [
	"Just a moment...",
	"Checking your browser",
	"Attention Required!",
	"Cloudflare"
];
var TIMELINE = {
	RETENTION_MS: 2592e6,
	GC_INTERVAL_MS: 36e5
};
var VALIDATION = {
	ACTRESS_NAME_MAX_LENGTH: 20,
	ACTRESS_NAME_MIN_LENGTH: 2,
	JAV_ID_REGEX: /^(?=.*[A-Z])[A-Z0-9-]{1,10}-\d{2,8}$/i};
var EXTERNAL_URLS = {
	SUPJAV: "https://supjav.com/zh/?s={id}",
	MISSAV_FC2: "https://missav.ws/cn/fc2-ppv-{id}",
	MISSAV: "https://missav.ws/cn/{id}",
	JAVDB: "https://javdb.com/search?q={id}&f=all",
	JAVBUS: "https://www.javbus.com/{id}",
	JAVLIBRARY: "https://www.javlibrary.com/cn/vl_searchbyid.php?keyword={id}",
	DMM: "https://www.dmm.co.jp/search/=/searchstr={id}",
	FC2: "https://adult.contents.fc2.com/article/{id}/?tag=TXpZM05EY3lORFk9",
	FD2PPV: "https://fd2ppv.cc/articles/{id}",
	FC2DB: "https://fc2db.net/work/{id}/",
	FC2CMADB: "https://fc2cmadb.com/articles/{id}",
	SUKEBEI: "https://sukebei.nyaa.si/?f=0&c=0_0&q={id}",
	GOOGLE_LENS: "https://lens.google.com/uploadbyurl?url={url}",
	FOURHOI_BASE: "https://fourhoi.com"
};
var SCRAPER_URLS = {
	SUKEBEI_SEARCH: "https://sukebei.nyaa.si/?f=0&c=0_0&q={query}&s=seeders&o=desc",
	PAIPANCON_DETAIL: "https://paipancon.com/fc2daily/detail/FC2-PPV-{id}",
	PAIPANCON_BASE: "https://paipancon.com"
};
var MAGNET_CONFIG = {
	MAX_CONCURRENCY: 16,
	MAX_RETRIES: 2,
	RETRY_DELAY: 1e3,
	PREDICTIVE_LIMIT: 16,
	DEFAULT_TYPE: "fc2",
	SEARCH_TIMEOUT_MS: 6e4
};
var PREVIEW_BLACKLIST = [
	"moechat_ads.jpg",
	"mc.yandex.ru",
	"linglan_ad1.jpg"
];
var JAV_PREFIX_BLACKLIST = [
	"FC2",
	"PPV",
	"PAGE",
	"LIST",
	"NEW",
	"BEST",
	"FILE",
	"VIEW",
	"LOCALHOST",
	"HTTP",
	"HTTPS",
	"WWW"
];
/**
* Blacklisted actress name strings — exact matches and regex patterns.
* Used by `MediaUtils.cleanActressName()` to filter out navigation/UI text.
*/
var ACTRESS_BLACKLIST_STRINGS = [
	"首页",
	"分类",
	"我的",
	"搜索",
	"排行榜",
	"导航",
	"菜单",
	"更多",
	"全部",
	"女优",
	"无码",
	"有码",
	"素人",
	"流出",
	"破解",
	"解密",
	"合集",
	"个人拍摄",
	"個人撮影"
];
/** Regex-only blacklist patterns (entries not covered by exact string match above). */
var ACTRESS_BLACKLIST = [/Top\s*\d+/i, /^[\d\s]+$/];
var PATTERNS = {
	FC2_PPV_PREFIX: "FC2-PPV-",
	PAIPANCON_COVER: "cover.jpg",
	PAIPANCON_MAIN: "main.jpg",
	CENSORED_INDICATOR: "icon-mosaic_free color_free0",
	FD2PPV_SELLER_MARKER: "賣家"
};
/**
* Persistent storage key constants.
* - User settings: SETTINGS, LANGUAGE, CARD_DENSITY, ACCENT_COLOR, etc.
* - Sync state: LAST_SYNC_TS, LAST_AUTO_SYNC_TS, WEBDAV_LAST_ETAG, etc.
* - Credentials: SUPABASE_*, WEBDAV_*
* - Internal runtime: DEBUG_MODE, etc.
*/
var STORAGE_KEYS = exports("j",{
	SETTINGS: "settings_v1",
	SUPABASE_URL: "supabase_url",
	SUPABASE_KEY: "supabase_key",
	SUPABASE_EMAIL: "supabase_email",
	SUPABASE_PASSWORD: "supabase_password",
	SUPABASE_JWT: "supabase_jwt",
	SUPABASE_REFRESH: "supabase_refresh_token",
	SYNC_USER_ID: "sync_user_id",
	LAST_SYNC_TS: "last_sync_ts",
	LAST_AUTO_SYNC_TS: "last_auto_sync_ts",
	LAST_SETTINGS_TAB: "last_settings_tab",
	WEBDAV_URL: "webdav_url",
	WEBDAV_USER: "webdav_user",
	WEBDAV_PASS: "webdav_pass",
	WEBDAV_PATH: "webdav_path",
	WEBDAV_LAST_ETAG: "webdav_last_etag",
	WEBDAV_SYNC_LOCK: "webdav_sync_lock",
	SYNC_MODE: "sync_mode",
	LANGUAGE: "language",
	USER_GRID_COLUMNS: "user_grid_columns_preference",
	FAB_POSITION: "fab_pos_v2",
	DEBUG_MODE: "fc2_debug_mode",
	CARD_DENSITY: "card_density",
	LAST_SYNC_RESULT: "last_sync_result",
	SETTINGS_COLLAPSED_GROUPS: "settings_collapsed_groups",
	SETTINGS_PRESET: "settings_preset",
	ACCENT_COLOR: "accent_color",
	FD2_BACKOFF_UNTIL: "fd2_backoff_until",
	LAST_ACTRESS_FETCH: "last_actress_fetch",
	CONFLICT_LOG: "sync_conflict_log"
});
var DATABASE = { NAME: "fc2_enhanced_db" };
var CACHE = {
	MEMORY_MAX_SIZE: 1e3,
	MEMORY_EXPIRATION_MS: 3e5,
	EXPIRATION_MS: 12096e5,
	PREVIEW_MAX_SIZE: 5,
	NEGATIVE_MAGNET_EXPIRATION_MS: 432e5
};
var SYNC_STATUS = {
	IDLE: "idle",
	SYNCING: "syncing",
	SUCCESS: "success",
	ERROR: "error",
	CONFLICT: "conflict"
};
var SUPABASE_ENDPOINTS = {
	TOKEN: "/auth/v1/token",
	SIGNUP: "/auth/v1/signup",
	USER_HISTORY: "/rest/v1/user_history"
};
var SYSTEM_KEYS = {
	MESSAGING_CHANNEL: "fc2-enhanced-sync"};
var UI_CONSTANTS = {
	DEFAULT_TIMESTAMP: "1970-01-01T00:00:00.000Z",
	DEFAULT_SYNC_FILENAME: "fc2_enhanced_sync.json",
	PLACEHOLDER_IMAGE: "data:image/svg+xml,%3Csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"400\" height=\"300\" fill=\"%23333\"%3E%3Crect width=\"400\" height=\"300\"/%3E%3Ctext x=\"50%25\" y=\"50%25\" dominant-baseline=\"middle\" text-anchor=\"middle\" fill=\"%23888\" font-family=\"sans-serif\" font-size=\"16\"%3ENo Image%3C/text%3E%3C/svg%3E",
	SWIPE_DISMISS_THRESHOLD: 100
};
var DOM_IDS = exports("y",{
	UI_HOST: "fc2-modern-ui-host",
	SETTINGS_HOST: "fc2-enh-settings-host",
	SETTINGS_CONTAINER: "fc2-enh-settings-container",
	TAB_CONTENT: "tab-content-container",
	LOG_LIST: "debug-log-list"
});
var CSS_CLASSES = {
	cardRebuilt: "fc2-card-rebuilt",
	processedCard: "fc2-processed-card",
	noMagnet: "fc2-no-magnet",
	hideNoMagnet: "fc2-hide-no-magnet",
	videoPreviewContainer: "fc2-video-preview-container",
	staticPreview: "fc2-static-preview",
	previewElement: "fc2-preview-element",
	hidden: "fc2-hidden",
	infoArea: "fc2-info-area",
	customTitle: "fc2-custom-card-title",
	fc2IdBadge: "fc2-id-badge",
	cardMeta: "fc2-card-meta",
	cardMetaItem: "fc2-card-meta-item",
	cardActionRow: "fc2-card-action-row",
	badgeCopied: "fc2-copied",
	preservedIconsContainer: "fc2-preserved-icons-container",
	resourceLinksContainer: "fc2-resource-links-container",
	resourceBtn: "fc2-resource-btn",
	btnLoading: "fc2-is-loading",
	btnMagnet: "fc2-magnet",
	tooltip: "fc2-tooltip",
	buttonText: "fc2-button-text",
	extraPreviewContainer: "fc2-preview-container",
	extraPreviewTitle: "fc2-preview-title",
	extraPreviewGrid: "fc2-preview-grid",
	isCensored: "fc2-is-censored",
	hideCensored: "fc2-hide-censored",
	enhDropdown: "fc2-enh-dropdown",
	isViewed: "fc2-is-viewed",
	hideViewed: "fc2-hide-viewed",
	isDownloaded: "fc2-is-downloaded",
	btnPlayFullscreen: "fc2-btn-play-fullscreen"
};
var Config = {
	EXTERNAL_URLS,
	SCRAPER_URLS,
	STORAGE_KEYS,
	TIMEOUTS: { API: 2e4 },
	CLASSES: CSS_CLASSES,
	COPIED_BADGE_DURATION: 1500
};
/** Extract HTTP status from a caught error object. Returns undefined if not an HTTP error. */
var getHttpStatus = (error) => {
	if (error && typeof error === "object" && "status" in error) return error.status;
};
var createAbortError = () => new DOMException("The request was aborted", "AbortError");
var attachAbort = (signal, onAbort) => {
	if (!signal) return () => {};
	if (signal.aborted) {
		onAbort();
		return () => {};
	}
	signal.addEventListener("abort", onAbort, { once: true });
	return () => signal.removeEventListener("abort", onAbort);
};
var http = (url, options = {}) => {
	const respType = options.responseType ?? options.type ?? "json";
	let data = options.data ?? options.body;
	if (data && typeof data === "object") data = JSON.stringify(data);
	if (options.signal?.aborted) return Promise.reject(createAbortError());
	return new Promise((resolve, reject) => {
		let requestHandle = null;
		const detachAbort = attachAbort(options.signal, () => {
			requestHandle?.abort?.();
			reject(createAbortError());
		});
		const settle = (fn) => {
			detachAbort();
			fn();
		};
		requestHandle = GM_xmlhttpRequest({
			method: options.method ?? "GET",
			url,
			headers: {
				...data ? { "Content-Type": "application/json" } : {},
				...options.headers ?? {}
			},
			data,
			timeout: options.timeout ?? Config.TIMEOUTS.API,
			responseType: respType === "json" ? "json" : "text",
			onload: (res) => {
				if (res.status >= 200 && res.status < 300) if (respType === "json") try {
					settle(() => resolve(res.response || JSON.parse(res.responseText)));
				} catch {
					settle(() => resolve(res.responseText));
				}
				else settle(() => resolve(res.responseText || res.response));
				else {
					const err = /* @__PURE__ */ new Error(`HTTP ${res.status}: ${res.statusText}`);
					err.status = res.status;
					err.response = res.responseText;
					settle(() => reject(err));
				}
			},
			onerror: (err) => {
				const error = /* @__PURE__ */ new Error(`Network Error: ${err.statusText || "Unknown"}`);
				error.status = 0;
				error.response = err;
				settle(() => reject(error));
			},
			ontimeout: () => {
				const err = /* @__PURE__ */ new Error("Timeout");
				err.status = 408;
				settle(() => reject(err));
			}
		});
	});
};
/**
* Fetches a resource as a Blob via GM_xmlhttpRequest to bypass CORS/CORP.
*/
var normalizeBlob = async (value) => {
	if (value instanceof Blob) return value;
	if (typeof value === "object" && value !== null && "arrayBuffer" in value && typeof value.arrayBuffer === "function") {
		const val = value;
		return new Blob([await val.arrayBuffer()], { type: val.type ?? "" });
	}
	throw new Error("Response was not a Blob");
};
var fetchBlob = (url, headers = {}, signal) => {
	if (signal?.aborted) return Promise.reject(createAbortError());
	return new Promise((resolve, reject) => {
		let requestHandle = null;
		const detachAbort = attachAbort(signal, () => {
			requestHandle?.abort?.();
			reject(createAbortError());
		});
		const settle = (fn) => {
			detachAbort();
			fn();
		};
		requestHandle = GM_xmlhttpRequest({
			method: "GET",
			url,
			headers,
			responseType: "blob",
			timeout: Config.TIMEOUTS.API,
			onload: (res) => {
				if (res.status >= 200 && res.status < 300) normalizeBlob(res.response).then((value) => settle(() => resolve(value)), (error) => settle(() => reject(error instanceof Error ? error : new Error(String(error)))));
				else {
					const err = /* @__PURE__ */ new Error(`HTTP ${res.status}: ${res.statusText}`);
					err.status = res.status;
					settle(() => reject(err));
				}
			},
			onerror: () => settle(() => reject(/* @__PURE__ */ new Error("Network error"))),
			ontimeout: () => settle(() => reject(/* @__PURE__ */ new Error("Timeout")))
		});
	});
};
/**
* Loads an image via GM_xmlhttpRequest and returns a local blob: URL.
* Useful for bypassing ERR_BLOCKED_BY_RESPONSE.NotSameOrigin (CORP).
*
* **Caller responsibility:** The returned blob: URL must be revoked via
* `URL.revokeObjectURL()` when no longer needed (e.g. after the image loads
* or the element is removed) to avoid memory leaks.
*/
var fetchImageAsBlobUrl = async (url, headers = {}, signal) => {
	const blob = await fetchBlob(url, headers, signal);
	return URL.createObjectURL(blob);
};
var mediaBlobUrls = /* @__PURE__ */ new WeakMap();
var liveBlobUrls = /* @__PURE__ */ new Set();
var trackBlobUrl = (blobUrl) => {
	liveBlobUrls.add(blobUrl);
};
var untrackBlobUrl = (blobUrl) => {
	liveBlobUrls.delete(blobUrl);
};
var assignMediaBlobUrl = (el, blobUrl) => {
	const previous = mediaBlobUrls.get(el);
	if (previous && previous !== blobUrl) {
		URL.revokeObjectURL(previous);
		untrackBlobUrl(previous);
	}
	mediaBlobUrls.set(el, blobUrl);
	trackBlobUrl(blobUrl);
	el.src = blobUrl;
};
/** Revoke every blob URL created via assignMediaBlobUrl (page hide / teardown). */
var revokeAllMediaBlobUrls = () => {
	liveBlobUrls.forEach((url) => {
		try {
			URL.revokeObjectURL(url);
		} catch {}
	});
	liveBlobUrls.clear();
};
/**
* Returns appropriate Referer headers for known domains to bypass anti-hotlinking.
*/
var getBypassHeaders = (url) => {
	const headers = {};
	if (url.includes("paipancon.com")) headers["Referer"] = "https://paipancon.com/";
	if (url.includes("fd2ppv.cc")) headers["Referer"] = "https://fd2ppv.cc/";
	if (url.includes("fourhoi.com")) headers["Referer"] = "https://fourhoi.com/";
	return headers;
};
var runMediaBypass = async (el, fallbacks, listener) => {
	const currentSrc = el.src;
	if (!currentSrc || currentSrc.startsWith("blob:") || currentSrc.includes("data:image") || currentSrc.includes("placeholder")) return;
	el.removeEventListener("error", listener);
	try {
		const blobUrl = await fetchImageAsBlobUrl(currentSrc, getBypassHeaders(currentSrc));
		el.addEventListener("error", listener, { once: true });
		assignMediaBlobUrl(el, blobUrl);
	} catch {
		const nextFallback = fallbacks.find((f) => f && f !== currentSrc);
		if (nextFallback) {
			const remaining = fallbacks.slice(fallbacks.indexOf(nextFallback) + 1);
			el.addEventListener("error", listener, { once: true });
			el.src = nextFallback;
			if (remaining.length > 0) attachMediaBypass(el, ...remaining);
		}
	}
};
/**
* Attaches an error handler to an image or video element to attempt a GM bypass if it fails to load.
* Supports multiple fallbacks in sequence.
*/
var attachMediaBypass = (el, ...fallbacks) => {
	const handleMediaError = () => {
		runMediaBypass(el, fallbacks, handleMediaError);
	};
	el.addEventListener("error", handleMediaError, { once: true });
};
/**
* Smartly loads media: proactive GM bypass for known problematic domains,
* and standard load with error-triggered bypass for others.
* Supports a sequence of fallback URLs.
*/
var smartLoadMedia = (el, src, ...fallbacks) => {
	if (!src || src.startsWith("blob:") || src.includes("data:image")) {
		if (src) el.src = src;
		return;
	}
	if (src.includes("paipancon.com") || src.includes("fourhoi.com")) fetchImageAsBlobUrl(src, getBypassHeaders(src)).then((blobUrl) => {
		assignMediaBlobUrl(el, blobUrl);
	}).catch(() => {
		const nextFallback = fallbacks.find((f) => f && f !== src);
		if (nextFallback) smartLoadMedia(el, nextFallback, ...fallbacks.slice(fallbacks.indexOf(nextFallback) + 1));
	});
	else {
		el.src = src;
		attachMediaBypass(el, ...fallbacks);
	}
};
var LogLevel = exports("z",/* @__PURE__ */ function(LogLevel) {
	LogLevel[LogLevel["SILENT"] = 0] = "SILENT";
	LogLevel[LogLevel["ERROR"] = 1] = "ERROR";
	LogLevel[LogLevel["WARN"] = 2] = "WARN";
	LogLevel[LogLevel["INFO"] = 3] = "INFO";
	LogLevel[LogLevel["DEBUG"] = 4] = "DEBUG";
	LogLevel[LogLevel["TRACE"] = 5] = "TRACE";
	return LogLevel;
}({}));
var PREFIX = "[FC2-ENH]";
var DEDUP_WINDOW_MS = 2e3;
var THROTTLE_WINDOW_MS = 5e3;
var LEVEL_NAMES = {
	[LogLevel.SILENT]: "SILENT",
	[LogLevel.ERROR]: "ERROR",
	[LogLevel.WARN]: "WARN",
	[LogLevel.INFO]: "INFO",
	[LogLevel.DEBUG]: "DEBUG",
	[LogLevel.TRACE]: "TRACE"
};
var formatTime = () => {
	const date = /* @__PURE__ */ new Date();
	return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}:${String(date.getSeconds()).padStart(2, "0")}.${String(date.getMilliseconds()).padStart(3, "0")}`;
};
var makeKey = (level, module, message) => `${level}|${module}|${message}`;
var shouldOutputLogLevel = (level, currentLevel) => level <= currentLevel;
var buildLogEntry = (level, module, message, data, traceId) => ({
	level,
	levelName: LEVEL_NAMES[level] ?? "UNKNOWN",
	module,
	message,
	timestamp: formatTime(),
	count: 1,
	...traceId ? { traceId } : {},
	...data !== void 0 ? { data } : {}
});
var appendHistoryEntry = (history, entry) => {
	history.push(entry);
	if (history.length > 500) history.splice(0, history.length - 500);
};
var formatLogTag = (entry) => {
	return `${PREFIX}${entry.traceId ? ` [${entry.traceId}]` : ""} [${entry.levelName}] [${entry.module}]`;
};
var outputLogEntry = (entry, currentLevel) => {
	if (!shouldOutputLogLevel(entry.level, currentLevel)) return;
	const args = [formatLogTag(entry), entry.message];
	if (entry.data !== void 0) args.push(entry.data);
	switch (entry.level) {
		case LogLevel.ERROR:
			console.error(...args);
			break;
		case LogLevel.WARN:
			console.warn(...args);
			break;
		case LogLevel.DEBUG:
		case LogLevel.TRACE:
			console.debug(...args);
			break;
		default: console.log(...args);
	}
};
var createThrottleNoticeEntry = (suppressed) => ({
	level: LogLevel.DEBUG,
	levelName: "DEBUG",
	module: "Throttle",
	message: `Suppressed ${suppressed} repeated messages in ${THROTTLE_WINDOW_MS}ms`,
	timestamp: formatTime(),
	count: 1
});
var SENSITIVE_KEY_RE = /(authorization|token|secret|password|passwd|api[-_]?key|jwt|cookie|refresh)/i;
var EMAIL_RE = /([A-Z0-9._%+-]{2})[A-Z0-9._%+-]*@([A-Z0-9.-]+\.[A-Z]{2,})/gi;
var QUERY_SECRET_RE = /([?&](?:token|key|apikey|api_key|auth|signature|password)=)([^&]+)/gi;
var MAX_SANITIZE_DEPTH = 8;
var CIRCULAR_MARKER = "[Circular]";
var DEPTH_MARKER = "[MaxDepth]";
var DOM_NODE_MARKER = "[DOM Node]";
var isDomNode = (value) => {
	if (typeof Node !== "undefined" && value instanceof Node) return true;
	return typeof value.nodeType === "number" && typeof value.nodeName === "string";
};
var maskLogString = (value) => value.replace(EMAIL_RE, (_match, user, domain) => `${user}***@${domain}`).replace(QUERY_SECRET_RE, (_match, prefix) => `${prefix}***`).replace(/Bearer\s+[A-Za-z0-9._-]+/gi, "Bearer ***");
var sanitizeLogData = (value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) => {
	if (typeof value === "string") return maskLogString(value);
	if (Array.isArray(value)) {
		if (depth >= MAX_SANITIZE_DEPTH) return DEPTH_MARKER;
		if (seen.has(value)) return CIRCULAR_MARKER;
		seen.add(value);
		const result = value.map((item) => sanitizeLogData(item, depth + 1, seen));
		seen.delete(value);
		return result;
	}
	if (!value || typeof value !== "object") return value;
	if (isDomNode(value)) return DOM_NODE_MARKER;
	if (depth >= MAX_SANITIZE_DEPTH) return DEPTH_MARKER;
	if (seen.has(value)) return CIRCULAR_MARKER;
	seen.add(value);
	const result = Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, SENSITIVE_KEY_RE.test(key) ? "***" : sanitizeLogData(entryValue, depth + 1, seen)]));
	seen.delete(value);
	return result;
};
var exportSanitizedLogHistory = (history, options = {}) => {
	const filtered = history.filter((entry) => !options.traceId || entry.traceId === options.traceId);
	if (!options.sanitized) return filtered.map((entry) => ({ ...entry }));
	return filtered.map((entry) => ({
		...entry,
		message: maskLogString(entry.message),
		data: entry.data !== void 0 ? sanitizeLogData(entry.data) : void 0
	}));
};
var LoggerImpl = class {
	_enabled = false;
	_history = [];
	_level = LogLevel.WARN;
	_timers = /* @__PURE__ */ new Map();
	_traceCounter = 0;
	_groupDepth = 0;
	_scopeCache = /* @__PURE__ */ new Map();
	_dedup = /* @__PURE__ */ new Map();
	_dedupFlushTimer = null;
	_throttle = /* @__PURE__ */ new Map();
	get enabled() {
		return this._enabled;
	}
	get history() {
		this._flushDedup();
		return [...this._history];
	}
	get traceId() {
		return `t-${++this._traceCounter}`;
	}
	get level() {
		return this._level;
	}
	init() {
		try {
			if (typeof GM_getValue !== "undefined" ? GM_getValue("fc2_debug_mode", false) : false) this.enable(false);
		} catch {}
	}
	enable(persist = true) {
		this._enabled = true;
		this._level = LogLevel.TRACE;
		this._persistDebugMode(persist, true);
	}
	disable(persist = true) {
		this._enabled = false;
		this._level = LogLevel.WARN;
		this._persistDebugMode(persist, false);
	}
	setLevel(level) {
		this._level = level;
	}
	clear() {
		this._history.length = 0;
		this._dedup.clear();
		this._throttle.clear();
	}
	scope(module) {
		let scoped = this._scopeCache.get(module);
		if (!scoped) {
			scoped = {
				error: (message, data, traceId) => this.error(module, message, data, traceId),
				warn: (message, data, traceId) => this.warn(module, message, data, traceId),
				info: (message, data, traceId) => this.info(module, message, data, traceId),
				debug: (message, data, traceId) => this.debug(module, message, data, traceId),
				trace: (message, data, traceId) => this.trace(module, message, data, traceId),
				success: (message, data, traceId) => this.info(module, message, data, traceId)
			};
			this._scopeCache.set(module, scoped);
		}
		return scoped;
	}
	time(label) {
		this._timers.set(label, performance.now());
	}
	timeEnd(label) {
		const start = this._timers.get(label);
		if (start === void 0) return;
		this._timers.delete(label);
		const message = `${label}: ${(performance.now() - start).toFixed(2)}ms`;
		this._push(LogLevel.DEBUG, "Timer", message);
		return message;
	}
	log(module, message, data, traceId) {
		this._push(LogLevel.INFO, module, message, data, traceId);
	}
	info(module, message, data, traceId) {
		this._push(LogLevel.INFO, module, message, data, traceId);
	}
	warn(module, message, data, traceId) {
		this._push(LogLevel.WARN, module, message, data, traceId);
	}
	error(module, message, data, traceId) {
		this._push(LogLevel.ERROR, module, message, data, traceId);
	}
	success(module, message, data, traceId) {
		this._push(LogLevel.INFO, module, message, data, traceId);
	}
	debug(module, message, data, traceId) {
		this._push(LogLevel.DEBUG, module, message, data, traceId);
	}
	trace(module, message, data, traceId) {
		this._push(LogLevel.TRACE, module, message, data, traceId);
	}
	group(module, label, traceId) {
		this._groupDepth += 1;
		this._push(LogLevel.INFO, module, `[group:start] ${label}`, void 0, traceId);
		if (this._shouldOutput(LogLevel.INFO)) console.group(`${PREFIX} [${module}] ${label}`);
	}
	groupEnd() {
		if (this._groupDepth <= 0) return;
		this._groupDepth -= 1;
		if (this._enabled) console.groupEnd();
	}
	_persistDebugMode(persist, enabled) {
		if (!persist) return;
		try {
			if (typeof GM_setValue !== "undefined") GM_setValue("fc2_debug_mode", enabled);
		} catch {}
	}
	_shouldOutput(level) {
		return shouldOutputLogLevel(level, this._level);
	}
	_push(level, module, message, data, traceId) {
		if (level > this._level && level !== LogLevel.ERROR) return;
		const sanitizedMessage = maskLogString(message);
		const sanitizedData = data !== void 0 ? sanitizeLogData(data) : void 0;
		const key = makeKey(level, module, sanitizedMessage);
		if (level >= LogLevel.DEBUG && this._checkThrottle(key)) return;
		const existing = this._dedup.get(key);
		const now = Date.now();
		if (existing && now - existing.firstTs < 2e3) {
			existing.count += 1;
			existing.lastEntry.count = existing.count;
			if (data !== void 0) existing.lastEntry.data = sanitizeLogData(data);
			this._scheduleDedupFlush();
			return;
		}
		this._flushDedup();
		const entry = buildLogEntry(level, module, sanitizedMessage, sanitizedData, traceId);
		this._dedup.set(key, {
			count: 1,
			firstTs: now,
			flushed: false,
			lastEntry: entry
		});
		this._record(entry);
		this._output(entry);
	}
	_scheduleDedupFlush() {
		if (this._dedupFlushTimer) return;
		this._dedupFlushTimer = setTimeout(() => {
			this._dedupFlushTimer = null;
			this._flushDedup();
		}, DEDUP_WINDOW_MS);
	}
	_flushDedup() {
		if (this._dedupFlushTimer) {
			clearTimeout(this._dedupFlushTimer);
			this._dedupFlushTimer = null;
		}
		for (const state of this._dedup.values()) if (state.count > 1 && !state.flushed) {
			state.flushed = true;
			state.lastEntry.count = state.count;
			if (this._shouldOutput(state.lastEntry.level)) console.debug(`${formatLogTag(state.lastEntry)} (x${state.count}) ${state.lastEntry.message}`);
		}
		const now = Date.now();
		for (const [key, state] of this._dedup) if (now - state.firstTs > 2e3) this._dedup.delete(key);
	}
	_checkThrottle(key) {
		const now = Date.now();
		let state = this._throttle.get(key);
		if (!state || now - state.windowStart > 5e3) {
			state = {
				count: 1,
				suppressed: 0,
				windowStart: now
			};
			this._throttle.set(key, state);
			return false;
		}
		state.count += 1;
		if (state.count <= 3) return false;
		state.suppressed += 1;
		if (state.suppressed === 1) this._scheduleThrottleSummary(key, state, now);
		return true;
	}
	_scheduleThrottleSummary(key, state, now) {
		setTimeout(() => {
			if (state.suppressed > 0) {
				const entry = createThrottleNoticeEntry(state.suppressed);
				this._record(entry);
				if (this._shouldOutput(LogLevel.DEBUG)) console.debug(formatLogTag(entry), entry.message);
			}
			this._throttle.delete(key);
		}, THROTTLE_WINDOW_MS - (now - state.windowStart));
	}
	_record(entry) {
		appendHistoryEntry(this._history, entry);
	}
	_output(entry) {
		outputLogEntry(entry, this._level);
	}
};
var Logger = exports("L",new LoggerImpl());
var exportLogHistory = exports("E",(options = {}) => exportSanitizedLogHistory(Logger.history, options));
var log$54 = Logger.scope("Container");
/** Create a typed service token */
var createToken = (description) => Symbol(description);
/** AppContainer — token-only DI + lifecycle */
var Container = class {
	tokens = /* @__PURE__ */ new Map();
	tokenNames = /* @__PURE__ */ new Map();
	initialized = false;
	bootstrapPromise = null;
	/**
	* Register a service by typed token (only registration API).
	*/
	registerToken(token, service) {
		if (this.tokens.has(token)) log$54.warn(`Token ${token.description ?? String(token)} already registered, overwriting`);
		this.tokens.set(token, service);
		this.tokenNames.set(token, token.description ?? String(token));
		return service;
	}
	/**
	* Resolve a service by token (throws if missing).
	*/
	resolve(token) {
		const service = this.tokens.get(token);
		if (!service) throw new Error(`[Container] Service not resolved: ${token.description ?? String(token)}`);
		return service;
	}
	/**
	* Try to resolve a service by token.
	*/
	tryResolve(token) {
		return this.tokens.get(token);
	}
	/**
	* System bootstrap with single-flight guard.
	*/
	async bootstrap() {
		if (this.initialized) return;
		if (this.bootstrapPromise) return this.bootstrapPromise;
		this.bootstrapPromise = this.doBootstrap();
		return this.bootstrapPromise;
	}
	async doBootstrap() {
		Logger.group("Container", "System bootstrap");
		try {
			const allServices = this.collectAllServices();
			log$54.debug("Stage 1: Initializing services");
			for (const [token, service] of allServices) if (service.onInit) try {
				await service.onInit();
				log$54.debug(`Initialized: ${this.nameOf(token)}`);
			} catch (error) {
				log$54.error(`Failed to initialize service: ${this.nameOf(token)}`, error);
				throw error;
			}
			log$54.debug("Stage 2: Orchestrating bootstrap");
			for (const [token, service] of allServices) if (service.onBootstrap) try {
				await service.onBootstrap();
				log$54.debug(`Bootstrapped: ${this.nameOf(token)}`);
			} catch (error) {
				log$54.error(`Failed to bootstrap service: ${this.nameOf(token)}`, error);
				throw error;
			}
			this.initialized = true;
			log$54.info("System bootstrap complete");
		} catch (error) {
			this.bootstrapPromise = null;
			log$54.error("Bootstrap failed", error);
			throw error;
		} finally {
			Logger.groupEnd();
		}
	}
	/**
	* Teardown all services; cleanup errors are isolated.
	*/
	async shutdown() {
		log$54.info("Shutting down services");
		try {
			const allServices = this.collectAllServices();
			for (const [token, service] of allServices) if (service.onCleanup) try {
				await service.onCleanup();
			} catch (error) {
				log$54.error(`Service ${this.nameOf(token)} cleanup failed`, error);
			}
		} finally {
			this.tokens.clear();
			this.tokenNames.clear();
			this.initialized = false;
			this.bootstrapPromise = null;
		}
	}
	collectAllServices() {
		return Array.from(this.tokens.entries());
	}
	nameOf(token) {
		return this.tokenNames.get(token) ?? token.description ?? String(token);
	}
};
var AppContainer = new Container();
var log$53 = Logger.scope("Events");
var AppEvents = exports("A",/* @__PURE__ */ function(AppEvents) {
	AppEvents["BOOTSTRAP"] = "app:bootstrap";
	AppEvents["SERVICES_READY"] = "app:services-ready";
	AppEvents["UI_READY"] = "app:ui-ready";
	AppEvents["STATE_CHANGED"] = "app:state-changed";
	AppEvents["THEME_CHANGED"] = "ui:theme-changed";
	AppEvents["LANGUAGE_CHANGED"] = "ui:language-changed";
	AppEvents["GRID_CHANGED"] = "ui:grid-changed";
	AppEvents["SYNC_STATUS_CHANGED"] = "sync:status-changed";
	AppEvents["HISTORY_LOADED"] = "history:loaded";
	AppEvents["HISTORY_ADDED"] = "history:added";
	AppEvents["HISTORY_REMOVED"] = "history:removed";
	AppEvents["HISTORY_CLEARED"] = "history:cleared";
	AppEvents["SITE_RESET"] = "site:reset";
	AppEvents["SITE_READY"] = "site:ready";
	AppEvents["MAGNET_FOUND"] = "magnet:found";
	AppEvents["MAGNET_FAILED"] = "magnet:failed";
	AppEvents["CARD_READY"] = "card:ready";
	AppEvents["VIEW_STATE_CHANGED"] = "view:state-changed";
	AppEvents["COLLECTION_HEALTH_PROGRESS"] = "collection:health-progress";
	AppEvents["COLLECTION_UPDATED"] = "collection:updated";
	AppEvents["COLLECTION_STATS_CHANGED"] = "collection:stats-changed";
	AppEvents["COLLECTION_EXPORT"] = "collection:export";
	AppEvents["COLLECTION_IMPORT"] = "collection:import";
	AppEvents["SHOW_TOAST"] = "ui:show-toast";
	AppEvents["OPEN_SETTINGS"] = "ui:open-settings";
	AppEvents["PANEL_OPENED"] = "ui:panel-opened";
	AppEvents["PANEL_CLOSED"] = "ui:panel-closed";
	AppEvents["HISTORY_CHANGED"] = "history:changed";
	AppEvents["FILTER_COUNTS_CHANGED"] = "ui:filter-counts-changed";
	AppEvents["CONFIG_CHANGED"] = "ui:config-changed";
	AppEvents["SELECTION_CHANGED"] = "selection:changed";
	AppEvents["CARD_DISCOVERED"] = "card:discovered";
	AppEvents["VIEW_REFRESH_REQUESTED"] = "view:refresh-requested";
	AppEvents["DETAIL_ENHANCEMENT_REQUESTED"] = "site:detail-enhancement-requested";
	AppEvents["HISTORY_STATUS_NEEDED"] = "history:status-needed";
	AppEvents["VISIBILITY_REFRESH_NEEDED"] = "ui:visibility-refresh-needed";
	AppEvents["CROSS_TAB_BROADCAST"] = "messaging:cross-tab-broadcast";
	return AppEvents;
}({}));
var CoreEventsImpl = class {
	handlers = /* @__PURE__ */ new Map();
	on(event, handler) {
		if (!this.handlers.has(event)) this.handlers.set(event, /* @__PURE__ */ new Set());
		this.handlers.get(event).add(handler);
		return () => this.off(event, handler);
	}
	off(event, handler) {
		const handlersSet = this.handlers.get(event);
		if (handlersSet) handlersSet.delete(handler);
	}
	emit(event, data) {
		log$53.trace(`Emit: ${event}`, data);
		const handlersSet = this.handlers.get(event);
		if (handlersSet) handlersSet.forEach((handler) => {
			try {
				handler(data);
			} catch (e) {
				log$53.error(`Handler error for ${event}`, e);
			}
		});
	}
};
var CoreEvents = exports("C",new CoreEventsImpl());
/** Resolve a registered service token (throws if missing). */
function resolveService(token) {
	return AppContainer.resolve(token);
}
var DEFAULT_LOCALE = "en-US";
var LEGACY_LOCALE_MAP = {
	zh: "zh-CN",
	en: "en-US",
	ja: "ja-JP"
};
var normalizeLocalePreference = (preference) => {
	if (!preference || preference === "auto") return "auto";
	if (Object.prototype.hasOwnProperty.call(LEGACY_LOCALE_MAP, preference)) {
		const legacyLocale = LEGACY_LOCALE_MAP[preference];
		if (legacyLocale) return legacyLocale;
	}
	const normalized = preference.toLowerCase();
	if (normalized.startsWith("zh")) return "zh-CN";
	if (normalized.startsWith("en")) return "en-US";
	if (normalized.startsWith("ja")) return "ja-JP";
	return DEFAULT_LOCALE;
};
var resolveBrowserLocale = (browserLocale) => {
	const normalized = (browserLocale || "en-US").toLowerCase();
	if (normalized.startsWith("zh")) return "zh-CN";
	if (normalized.startsWith("ja")) return "ja-JP";
	return "en-US";
};
var resolveLocalePreference = (preference, browserLocale = DEFAULT_LOCALE) => {
	if (!preference || preference === "auto") return resolveBrowserLocale(browserLocale);
	return normalizeLocalePreference(preference);
};
var baseTranslations = {
	"zh-CN": {
		managementCenter: "管理中心",
		tabSettings: "基本设置",
		tabStatistics: "数据概览",
		tabData: "同步与导出",
		tabAbout: "关于",
		tabDmca: "免责声明",
		groupFilters: "内容过滤",
		optionHideNoMagnet: "隐藏无磁力资源",
		optionHideCensored: "过滤有码内容",
		optionHideViewed: "过滤已阅内容",
		optionHideBlocked: "屏蔽黑名单内容",
		groupAppearance: "界面与交互",
		groupExternalPortals: "外部站点",
		labelPreviewMode: "预览功能",
		labelCardDensity: "卡片密度",
		labelAccentColor: "主题色",
		accentColorDefault: "经典白",
		accentColorPurple: "极光紫",
		accentColorPink: "落樱粉",
		accentColorBlue: "深海蓝",
		accentColorGreen: "翡翠绿",
		accentColorOrange: "活力橙",
		accentColorGold: "皇家金",
		previewModeStatic: "静态封面",
		previewModeHover: "悬停/点击播放预览",
		cardDensityCoverOnly: "仅封面",
		cardDensityCompact: "紧凑",
		cardDensityStandard: "标准",
		cardDensityImmersive: "沉浸",
		labelGridColumns: "网格排版",
		labelDefault: "默认",
		labelLanguage: "显示语言",
		langAuto: "跟随浏览器",
		langZh: "简体中文",
		langEn: "English",
		langJa: "日本語",
		groupDataHistory: "功能增强",
		groupCardActions: "卡片快捷按钮",
		cardActionDirectHint: "勾选后直出显示,未勾选则收进更多菜单。",
		cardActionId: "复制番号",
		cardActionViewed: "已阅状态",
		cardActionMagnet: "磁力搜索",
		cardActionPlay: "视频预览",
		cardActionPreview: "图集预览",
		cardActionExternal: "外部站点",
		optionEnableHistory: "记录浏览历史",
		optionLoadExtraPreviews: "开启图集预览抓取 (Paipancon)",
		optionEnableQuickBar: "显示右下角悬浮球",
		optionShowViewedBtn: "卡片显示已阅开关",
		optionShowIdBadge: "卡片显示番号复制按钮",
		optionEnableMagnets: "聚合 Sukebei 的磁力搜索",
		optionEnableExternalLinks: "开启外部站点跳转",
		optionSupjavSortByViews: "Supjav 列表按播放量排序",
		labelCacheManagement: "缓存管理",
		btnClearCache: "清理磁力搜索结果缓存",
		labelHistoryManagement: "历史管理",
		btnClearHistory: "清空所有历史记录",
		alertSettingsSaved: "设置已保存",
		alertCacheCleared: "磁力搜索结果已清理",
		alertHistoryCleared: "所有历史记录已清空",
		menuOpenSettings: "⚙️ 脚本配置",
		tooltipCopyMagnet: "复制磁力链接",
		tooltipCopied: "已复制",
		tooltipOpenPreviewGallery: "打开图集预览",
		tooltipOpenExternalLinks: "打开外部站点",
		tooltipHealthRecent: "近期已验证正常",
		tooltipHealthStale: "健康检查已过期",
		extraPreviewTitle: "图集预览",
		alertNoPreview: "未找到预览图",
		alertPreviewCfBlocked: "被反爬盾拦截,请先到源站验证",
		alertPreviewRateLimited: "请求过于频繁,请稍后再试",
		alertNoVideoPreview: "未找到视频预览",
		alertNoExternalLinks: "未找到外部站点",
		labelExternalLinks: "外部站点",
		groupDataManagement: "备份与恢复",
		btnExportData: "导出备份",
		btnImportData: "恢复备份",
		alertExportSuccess: "备份文件已开始下载",
		alertImportSuccess: "数据恢复成功,即将重新加载",
		alertImportError: "恢复失败:无效的备份文件",
		tooltipMarkAsViewed: "标为已阅",
		tooltipMarkAsUnviewed: "标为未阅",
		confirmResetDatabase: "确定重置脚本?这将清空所有本地数据与配置且无法恢复。",
		groupWebDAV: "WebDAV 同步",
		labelWebDAVUrl: "服务器地址",
		labelWebDAVUser: "账号",
		labelWebDAVPass: "密码/Token",
		btnWebDAVTest: "测试连接",
		btnWebDAVSync: "立即同步",
		btnSyncNow: "立即同步",
		btnForceSync: "强制重新同步",
		alertWebDAVSuccess: "连接成功",
		alertWebDAVError: "连接失败,请检查配置",
		alertWebDAVSyncSuccess: "云同步已完成",
		alertWebDAVSyncError: "同步失败:",
		alertSupabaseSyncSuccess: "云同步已完成",
		alertSupabaseSyncError: "同步失败:",
		syncStatus: "连接状态",
		labelSyncMode: "同步策略",
		syncModeNone: "关闭",
		syncModeSupabase: "Supabase 云端同步",
		syncModeWebDAV: "WebDAV 协议 (推荐)",
		labelLastSync: "上次同步时间",
		labelNever: "从未同步",
		labelSyncing: "同步中...",
		alertAlreadyUpToDate: "数据已同步到最新",
		alertSyncLocked: "同步任务已在其他标签页运行中",
		alertSyncLockActive: "其他标签页正在更新,请稍候",
		labelSyncInterval: "自动同步频率",
		syncInterval0: "实时同步(每次操作后同步)",
		syncInterval2: "每 2 分钟",
		syncInterval5: "每 5 分钟 (推荐)",
		syncInterval10: "每 10 分钟",
		syncInterval30: "每 30 分钟",
		syncIntervalManual: "仅手动触发",
		labelAuthEmail: "邮箱",
		labelAuthPass: "密码",
		btnConnectAndSync: "登录并同步",
		btnPullSync: "仅强制拉取云端数据",
		btnLogout: "注销",
		alertLoginRequired: "请填写登录信息",
		alertSbUrlRequired: "缺少 Supabase 配置信息",
		alertSyncAccountConnected: "账号连接成功",
		alertPushAllQuery: "确定将本地数据全量推送到云端?",
		alertPullAllQuery: "确定从云端覆盖本地数据?这将丢弃所有本地未同步的改动。",
		labelSupabaseSync: "Supabase 同步",
		labelSupabaseUrl: "服务器地址",
		labelSupabaseKey: "密钥 (anon key)",
		aboutDescription: "为 FC2PPVDB 等站点提供磁力评分聚合、高清封面替换、图集模式及历史记录同步。",
		aboutHelpTitle: "主要功能",
		aboutHelpContent: "聚合 Sukebei 磁力搜索,并自动过滤失效条目。\n自动补全 Fourhoi 高清封面,详情页支持图集预览及键盘操作。\n支持 WebDAV 或 Supabase 协议,安全同步多设备浏览历史。\n快捷悬浮球、一键复制番号、外部站点快速跳转。",
		aboutFooterTagline: "为效率与隐私而设计。",
		labelTechnicalLogs: "系统日志",
		btnCopyAll: "复制全部",
		btnSelectAll: "全选",
		btnDeselectAll: "取消全选",
		btnClearLogs: "清空日志",
		alertLogsCopied: "日志已复制到剪贴板",
		alertLogsCleared: "系统日志已清空",
		labelLogFilters: "日志过滤",
		btnCancel: "取消",
		btnSave: "保存设置",
		btnMoreOptions: "更多功能",
		btnClose: "关闭",
		btnBackToTop: "回顶部",
		labelDebugMode: "调试模式",
		statusDebugOn: "开发模式",
		statusDebugOff: "常规模式",
		alertDebugOn: "调试模式已激活",
		alertDebugOff: "已回到常规模式",
		btnCopyEnv: "导出诊断信息",
		tooltipCopyId: "复制番号",
		dmcaContent: "本脚本仅为本地增强型工具,<b>不托管或存储任何视频或图片文件</b>。所有索引内容均来自第三方公开网站。使用者需对使用行为涉及的法律风险自行负责。如有侵权内容展示,请联系该内容的来源网站发起移除。",
		labelDisclaimer: "声明条款",
		labelGreasyFork: "Greasy Fork 主页",
		labelLoading: "加载中...",
		labelPreview: "图集预览",
		labelPlayPreview: "播放视频预览",
		alertPreviewLoadFailed: "预览加载失败",
		toastNetworkRetrying: "网络错误,正在重试({attempt}/{total})...",
		alertLoggedOut: "已成功退出",
		mainPreview: "功能演示",
		gallerySearch: "以图搜图",
		gallerySlideshow: "开启幻灯片播放",
		searchPlaceholder: "在当前列表中搜索...",
		labelError: "发生错误",
		confirmDestructiveAction: "注意:此操作具有破坏性,执行后数据无法找回。",
		groupScraperDiagnosis: "爬虫连通性诊断",
		btnTestScrapers: "开始连通性诊断",
		scraperStatusPending: "待测试",
		scraperStatusOk: "连接正常",
		scraperStatusError: "连接失败",
		scraperStatusCF: "需要验证 CF",
		scraperStatus429: "请求频繁",
		scraperStatusLoading: "测试中...",
		scraperTargetSukebei: "Sukebei(磁力搜索)",
		scraperTargetFd2: "FD2PPV(演员抓取)",
		scraperTargetPaipancon: "Paipancon(图集预览)",
		alertScraperBlocked: "爬虫已被拦截:{target}",
		btnGoVerify: "去验证",
		settingsAdvancedSection: "高级选项",
		syncLatestTitle: "最近一次同步",
		syncLatestEmpty: "暂无同步结果。",
		syncCompletedAt: "完成时间",
		syncNoConflictYet: "当前没有冲突记录。",
		syncConflictLocalNewer: "本地较新: {count}",
		syncConflictRemoteNewer: "云端较新: {count}",
		syncConflictMerged: "已合并: {count}",
		debugTraceIdPlaceholder: "按 Trace ID 过滤",
		debugExportJson: "导出 JSON",
		debugPayload: "载荷",
		perfSyncExecute: "同步执行",
		perfUpdatedAt: "更新时间",
		syncActionDebugLogs: "调试日志",
		backupImportPreviewPlaceholder: "导入预览会显示在这里。",
		backupExportSuccessPlain: "已导出备份。",
		backupImportErrorInvalidJson: "备份文件不是有效的 JSON。",
		backupImportErrorInvalidPayload: "备份文件结构无效。",
		backupImportErrorUnsupportedVersion: "备份版本高于当前脚本,暂时无法导入。",
		backupImportErrorReadFailed: "读取备份文件失败。",
		backupImportErrorApplyFailed: "备份解析成功,但应用数据时失败。",
		backupImportConfirm: "确定导入这个备份吗?",
		backupSettingsAdded: "新增设置:{count}",
		backupSettingsChanged: "变更设置:{count}",
		backupHistoryRows: "历史记录:{count}",
		backupHistoryRowsWithCurrent: "历史记录:{count}(当前:{current})",
		backupChangedKeys: "变更键:{keys}",
		syncSummaryFailed: "同步失败",
		syncSummaryFailedWithMessage: "同步失败: {message}",
		syncSummaryCompleted: "同步完成",
		syncSummaryMode: "模式: {mode}",
		syncSummaryPushed: "上传 {count}",
		syncSummaryPulled: "下载 {count}",
		syncSummaryDirty: "脏数据 {count}",
		syncSummaryUpToDate: "无需同步,数据已是最新。",
		syncSummaryConflictRetry: "检测到冲突,已按较新版重试。",
		syncConflictWebdavRetry: "WebDAV ETag 冲突,系统已刷新云端状态后再次合并。",
		syncErrorSupabase: "Supabase 同步失败",
		syncErrorWebdav: "WebDAV 同步失败",
		syncErrorNoProvider: "未配置同步提供方",
		syncErrorLocked: "另一标签页中已有同步正在进行",
		settingsTaskSync: "同步任务",
		settingsTaskConfigure: "配置选项",
		settingsTaskDiagnose: "诊断工具",
		settingsTaskInfo: "关于信息",
		labelSelectionMode: "批量操作模式",
		batchActionMarkViewed: "标记已看",
		batchActionMarkUnviewed: "标记未看",
		batchActionCopyIds: "复制番号",
		batchActionExtractMagnets: "提取磁力",
		batchSelectionCount: "已选中 {count} 项",
		batchActionConfirm: "确认执行",
		batchActionCancel: "取消",
		batchExtractTitle: "批量提取磁力链接",
		batchExtractPlaceholder: "正在抓取磁力链接...",
		batchExtractProgress: "正在抓取磁力 {current}/{total}...",
		batchExtractPartial: "成功 {ok} 条,无结果/失败 {fail} 条",
		batchNoneSelected: "请先选择项目",
		batchExtractEmpty: "未找到磁力链接",
		batchExtractSuccess: "已成功提取 {count} 条磁力链接"
	},
	"en-US": {
		managementCenter: "Management Center",
		tabSettings: "Preferences",
		tabStatistics: "Analytics",
		tabData: "Data & Sync",
		tabAbout: "About",
		tabDmca: "Disclaimer",
		groupFilters: "Filters",
		optionHideNoMagnet: "Filter No-Magnet",
		optionHideCensored: "Filter Censored",
		optionHideViewed: "Filter Viewed",
		optionHideBlocked: "Filter Blocked",
		groupAppearance: "Appearance",
		groupExternalPortals: "External Sites",
		labelPreviewMode: "Preview Mode",
		labelCardDensity: "Card Density",
		labelAccentColor: "Accent Color",
		accentColorDefault: "Classic White",
		accentColorPurple: "Cyber Purple",
		accentColorPink: "Sakura Pink",
		accentColorBlue: "Deep Sea Blue",
		accentColorGreen: "Emerald Green",
		accentColorOrange: "Vibrant Orange",
		accentColorGold: "Royal Gold",
		previewModeStatic: "Static Cover",
		previewModeHover: "Hover/Click Play",
		cardDensityCoverOnly: "Cover Only",
		cardDensityCompact: "Compact",
		cardDensityStandard: "Standard",
		cardDensityImmersive: "Immersive",
		labelGridColumns: "Grid Columns",
		labelDefault: "Default",
		labelLanguage: "Language",
		langAuto: "Auto",
		langZh: "Chinese",
		langEn: "English",
		langJa: "Japanese",
		groupDataHistory: "Enhancements",
		groupCardActions: "Card Shortcuts",
		cardActionDirectHint: "Checked actions stay visible on the card; unchecked actions collapse into the more menu.",
		cardActionId: "Copy ID",
		cardActionViewed: "Toggle Viewed",
		cardActionMagnet: "Magnet Search",
		cardActionPlay: "Video Preview",
		cardActionPreview: "Gallery Preview",
		cardActionExternal: "External Sites",
		optionEnableHistory: "Track History",
		optionLoadExtraPreviews: "Enable Gallery Preview (Paipancon)",
		optionEnableQuickBar: "Show Quick FAB",
		optionShowViewedBtn: "Show Viewed Button on Card",
		optionShowIdBadge: "Show ID Badge on Card",
		optionEnableMagnets: "Aggregate Magnet Search (Sukebei)",
		optionEnableExternalLinks: "Show External Links",
		optionSupjavSortByViews: "Sort Supjav List by Views",
		labelCacheManagement: "Storage",
		btnClearCache: "Clear Magnet Search Cache",
		labelHistoryManagement: "History",
		btnClearHistory: "Clear History",
		alertSettingsSaved: "Settings saved",
		alertCacheCleared: "Magnet search cache cleared",
		alertHistoryCleared: "History cleared",
		menuOpenSettings: "⚙️ Settings",
		tooltipCopyMagnet: "Copy Magnet",
		tooltipCopied: "Copied",
		tooltipOpenPreviewGallery: "Open gallery preview",
		tooltipOpenExternalLinks: "Open external sites",
		tooltipHealthRecent: "Recently verified healthy",
		tooltipHealthStale: "Health check is stale",
		extraPreviewTitle: "Gallery Preview",
		alertNoPreview: "No Previews",
		alertPreviewCfBlocked: "Blocked by Cloudflare — verify on the source site first",
		alertPreviewRateLimited: "Rate limited — please try again later",
		alertNoVideoPreview: "No Video Previews",
		alertNoExternalLinks: "No external links found",
		labelExternalLinks: "External Sites",
		groupDataManagement: "Backup",
		btnExportData: "Export Backup",
		btnImportData: "Restore Backup",
		alertExportSuccess: "Backup started",
		alertImportSuccess: "Restore successful, refreshing...",
		alertImportError: "Restore failed: Invalid file",
		tooltipMarkAsViewed: "Mark viewed",
		tooltipMarkAsUnviewed: "Unmark viewed",
		confirmResetDatabase: "Reset script? This will wipe ALL local data and settings permanently.",
		groupWebDAV: "WebDAV Sync",
		labelWebDAVUrl: "Server URL",
		labelWebDAVUser: "Username",
		labelWebDAVPass: "Password/Token",
		btnWebDAVTest: "Test",
		btnWebDAVSync: "Sync Now",
		btnSyncNow: "Sync Now",
		btnForceSync: "Force Full Sync",
		alertWebDAVSuccess: "Connected",
		alertWebDAVError: "Connection failed. Check settings.",
		alertWebDAVSyncSuccess: "Sync Complete",
		alertWebDAVSyncError: "Sync Failed: ",
		alertSupabaseSyncSuccess: "Sync Complete",
		alertSupabaseSyncError: "Sync Failed: ",
		syncStatus: "Status",
		labelSyncMode: "Sync Strategy",
		syncModeNone: "Off",
		syncModeSupabase: "Supabase (Advanced)",
		syncModeWebDAV: "WebDAV (Recommended)",
		labelLastSync: "Last Sync",
		labelNever: "Never",
		labelSyncing: "Syncing...",
		alertAlreadyUpToDate: "Already up to date",
		alertSyncLocked: "Sync already in progress in another tab",
		alertSyncLockActive: "Sync in progress in another tab...",
		labelSyncInterval: "Auto-Sync Interval",
		syncInterval0: "Real-time (High load)",
		syncInterval2: "2 minutes",
		syncInterval5: "5 minutes (Recommended)",
		syncInterval10: "10 minutes",
		syncInterval30: "30 minutes",
		syncIntervalManual: "Manual only",
		labelAuthEmail: "Email",
		labelAuthPass: "Password",
		btnConnectAndSync: "Login",
		btnPullSync: "Force Pull Sync",
		btnLogout: "Logout",
		alertLoginRequired: "Credentials missing",
		alertSbUrlRequired: "URL missing",
		alertSyncAccountConnected: "Account connected",
		alertPushAllQuery: "Push all local data?",
		alertPullAllQuery: "Restore all data from cloud? This will overwrite local conflicts.",
		labelSupabaseSync: "Supabase",
		labelSupabaseUrl: "Supabase URL",
		labelSupabaseKey: "Supabase Key",
		aboutDescription: "Inject magnet links, HD covers, gallery mode, and history sync for FC2PPVDB, Supjav, and more.",
		aboutHelpTitle: "Features",
		aboutHelpContent: "• <b>Magnet Search</b>: Aggregate Sukebei results with smart filtering.\n• <b>HD Covers</b>: Auto-replace covers from Fourhoi, gallery preview with keyboard nav.\n• <b>Cloud Sync</b>: Sync history via WebDAV / Supabase across devices.\n• <b>Efficiency</b>: Quick FAB, instant ID copy, external link shortcuts.",
		aboutFooterTagline: "Designed for efficiency and privacy.",
		labelTechnicalLogs: "Technical Logs",
		btnCopyAll: "Copy All",
		btnSelectAll: "Select All",
		btnDeselectAll: "Deselect All",
		btnClearLogs: "Clear",
		alertLogsCopied: "Logs copied to clipboard",
		alertLogsCleared: "System logs cleared",
		labelLogFilters: "Filters",
		btnCancel: "Cancel",
		btnSave: "Save",
		btnMoreOptions: "More Options",
		btnClose: "Close",
		btnBackToTop: "Top",
		labelDebugMode: "Debug",
		statusDebugOn: "Debug ON",
		statusDebugOff: "Normal",
		alertDebugOn: "Debug Enabled",
		alertDebugOff: "Debug Disabled",
		btnCopyEnv: "Copy Diagnostics",
		tooltipCopyId: "Copy ID",
		dmcaContent: "This script is a utility tool and <b>does NOT host any video or image files</b>. All content (including images, magnet links) is provided by third-party public websites. Users assume full responsibility for using this tool. If content displayed by this tool infringes your rights, please contact the source website directly for removal.",
		labelDisclaimer: "Disclaimer",
		labelGreasyFork: "Greasy Fork",
		labelLoading: "Loading...",
		labelPreview: "Gallery Preview",
		labelPlayPreview: "Play Preview Video",
		alertPreviewLoadFailed: "Preview failed to load",
		toastNetworkRetrying: "Network error, retrying ({attempt}/{total})...",
		alertLoggedOut: "Logged out",
		mainPreview: "Feature Preview",
		gallerySearch: "Search Image",
		gallerySlideshow: "Slideshow",
		searchPlaceholder: "Search in collection...",
		labelError: "Error",
		confirmDestructiveAction: "Warning: Irreversible action. Continue?",
		groupScraperDiagnosis: "Scraper Connectivity",
		btnTestScrapers: "Run Diagnostics",
		scraperStatusPending: "Pending",
		scraperStatusOk: "Healthy",
		scraperStatusError: "Connection Failed",
		scraperStatusCF: "CF Blocked",
		scraperStatus429: "Rate Limited",
		scraperStatusLoading: "Testing...",
		scraperTargetSukebei: "Sukebei (Magnet Search)",
		scraperTargetFd2: "FD2PPV (Actress Fetch)",
		scraperTargetPaipancon: "Paipancon (Gallery Preview)",
		alertScraperBlocked: "Scraper blocked: {target}",
		btnGoVerify: "Verify Now",
		settingsAdvancedSection: "Advanced options",
		syncLatestTitle: "Latest sync",
		syncLatestEmpty: "No sync result yet.",
		syncCompletedAt: "Completed",
		syncNoConflictYet: "No conflicts reported yet.",
		syncConflictLocalNewer: "Local newer: {count}",
		syncConflictRemoteNewer: "Remote newer: {count}",
		syncConflictMerged: "Merged: {count}",
		debugTraceIdPlaceholder: "Filter by Trace ID",
		debugExportJson: "Export JSON",
		debugPayload: "Payload",
		perfSyncExecute: "Sync Execute",
		perfUpdatedAt: "Updated",
		syncActionDebugLogs: "Debug logs",
		backupImportPreviewPlaceholder: "Import preview will appear here.",
		backupExportSuccessPlain: "Backup exported.",
		backupImportErrorInvalidJson: "Backup file is not valid JSON.",
		backupImportErrorInvalidPayload: "Backup file structure is invalid.",
		backupImportErrorUnsupportedVersion: "Backup version is newer than this script and cannot be imported yet.",
		backupImportErrorReadFailed: "Failed to read the backup file.",
		backupImportErrorApplyFailed: "Backup parsed successfully, but applying it failed.",
		backupImportConfirm: "Import this backup?",
		backupSettingsAdded: "Settings added: {count}",
		backupSettingsChanged: "Settings changed: {count}",
		backupHistoryRows: "History rows: {count}",
		backupHistoryRowsWithCurrent: "History rows to import: {count} (current: {current})",
		backupChangedKeys: "Changed keys: {keys}",
		syncSummaryFailed: "Sync failed",
		syncSummaryFailedWithMessage: "Sync failed: {message}",
		syncSummaryCompleted: "Sync completed",
		syncSummaryMode: "Mode: {mode}",
		syncSummaryPushed: "Pushed {count}",
		syncSummaryPulled: "Pulled {count}",
		syncSummaryDirty: "Dirty {count}",
		syncSummaryUpToDate: "Already up to date",
		syncSummaryConflictRetry: "Conflict detected, retrying with fresh remote state",
		syncConflictWebdavRetry: "WebDAV ETag conflict detected. The app retried after refreshing remote state.",
		syncErrorSupabase: "Supabase sync failed",
		syncErrorWebdav: "WebDAV sync failed",
		syncErrorNoProvider: "No sync provider configured",
		syncErrorLocked: "Sync already in progress in another tab",
		settingsTaskSync: "Sync Tasks",
		settingsTaskConfigure: "Configure",
		settingsTaskDiagnose: "Diagnosis",
		settingsTaskInfo: "Information",
		labelSelectionMode: "Batch Selection Mode",
		batchActionMarkViewed: "Mark as Viewed",
		batchActionMarkUnviewed: "Mark as Unviewed",
		batchActionCopyIds: "Copy IDs",
		batchActionExtractMagnets: "Extract Magnets",
		batchSelectionCount: "{count} items selected",
		batchActionConfirm: "Execute",
		batchActionCancel: "Cancel",
		batchExtractTitle: "Batch Extract Magnets",
		batchExtractPlaceholder: "Fetching magnet links...",
		batchExtractProgress: "Fetching magnets {current}/{total}...",
		batchExtractPartial: "{ok} found, {fail} missing/failed",
		batchNoneSelected: "Select items first",
		batchExtractEmpty: "No magnets found",
		batchExtractSuccess: "Successfully extracted {count} magnets"
	},
	"ja-JP": {
		managementCenter: "管理センター",
		tabSettings: "基本設定",
		tabStatistics: "統計",
		tabData: "同期とエクスポート",
		tabAbout: "バージョン情報",
		tabDmca: "免責事項",
		groupFilters: "コンテンツフィルタ",
		optionHideNoMagnet: "マグネットなしを非表示",
		optionHideCensored: "有修正をフィルタ",
		optionHideViewed: "既読をフィルタ",
		optionHideBlocked: "ブラックリストをフィルタ",
		groupAppearance: "外観と操作",
		groupExternalPortals: "外部サイト",
		labelPreviewMode: "プレビュー機能",
		labelCardDensity: "カード密度",
		labelAccentColor: "アクセントカラー",
		accentColorDefault: "クラシックホワイト",
		accentColorPurple: "サイバーパープル",
		accentColorPink: "サクラピンク",
		accentColorBlue: "ディープシーブルー",
		accentColorGreen: "エメラルドグリーン",
		accentColorOrange: "ビブラントオレンジ",
		accentColorGold: "ロイヤルゴールド",
		previewModeStatic: "静止画カバー",
		previewModeHover: "ホバー/クリック再生",
		cardDensityCoverOnly: "カバーのみ",
		cardDensityCompact: "コンパクト",
		cardDensityStandard: "スタンダード",
		cardDensityImmersive: "イマーシブ",
		labelGridColumns: "グリッドレイアウト",
		labelDefault: "デフォルト",
		labelLanguage: "表示言語",
		langAuto: "ブラウザ設定に従う",
		langZh: "简体中文",
		langEn: "English",
		langJa: "日本語",
		groupDataHistory: "機能拡張",
		groupCardActions: "ショートカットボタン",
		cardActionDirectHint: "チェックした項目はカード上に直接表示されます。",
		cardActionId: "品番をコピー",
		cardActionViewed: "既読状態",
		cardActionMagnet: "マグネット検索",
		cardActionPlay: "動画プレビュー",
		cardActionPreview: "ギャラリー",
		cardActionExternal: "外部サイト",
		optionEnableHistory: "閲覧履歴を記録",
		optionLoadExtraPreviews: "ギャラリープレビューを有効化 (Paipancon)",
		optionEnableQuickBar: "クイック操作球を表示",
		optionShowViewedBtn: "既読スイッチを表示",
		optionShowIdBadge: "品番コピーボタンを表示",
		optionEnableMagnets: "Sukebei マグネット検索を統合",
		optionEnableExternalLinks: "外部サイトへのジャンプを有効化",
		optionSupjavSortByViews: "Supjav リストを再生数順にソート",
		labelCacheManagement: "キャッシュ管理",
		btnClearCache: "マグネット検索キャッシュを消去",
		labelHistoryManagement: "履歴管理",
		btnClearHistory: "すべての閲覧履歴を消去",
		alertSettingsSaved: "設定を保存しました",
		alertCacheCleared: "キャッシュを消去しました",
		alertHistoryCleared: "履歴を消去しました",
		menuOpenSettings: "⚙️ スクリプト設定",
		tooltipCopyMagnet: "マグネットをコピー",
		tooltipCopied: "コピー完了",
		tooltipOpenPreviewGallery: "ギャラリーを開く",
		tooltipOpenExternalLinks: "外部サイトを開く",
		tooltipHealthRecent: "最近の検証で正常",
		tooltipHealthStale: "検証データが古い",
		extraPreviewTitle: "プレビュー",
		alertNoPreview: "プレビューが見つかりません",
		alertPreviewCfBlocked: "Cloudflareにブロックされました。先に元サイトで認証してください",
		alertPreviewRateLimited: "リクエストが多すぎます。しばらくしてからお試しください",
		alertNoVideoPreview: "動画プレビューが見つかりません",
		alertNoExternalLinks: "外部リンクが見つかりません",
		labelExternalLinks: "外部サイト",
		groupDataManagement: "バックアップと復元",
		btnExportData: "バックアップをエクスポート",
		btnImportData: "バックアップから復元",
		alertExportSuccess: "エクスポートを開始しました",
		alertImportSuccess: "復元に成功しました。再読み込みします",
		alertImportError: "復元失敗:無効なファイルです",
		tooltipMarkAsViewed: "既読にする",
		tooltipMarkAsUnviewed: "未読にする",
		confirmResetDatabase: "スクリプトをリセットしますか?すべてのデータが消去されます。",
		groupWebDAV: "WebDAV 同期",
		labelWebDAVUrl: "サーバーURL",
		labelWebDAVUser: "ユーザー名",
		labelWebDAVPass: "パスワード/トークン",
		btnWebDAVTest: "接続テスト",
		btnWebDAVSync: "今すぐ同期",
		btnSyncNow: "同期実行",
		btnForceSync: "強制再同期",
		alertWebDAVSuccess: "接続成功",
		alertWebDAVError: "接続失敗。設定を確認してください",
		alertWebDAVSyncSuccess: "同期が完了しました",
		alertWebDAVSyncError: "同期失敗:",
		alertSupabaseSyncSuccess: "同期が完了しました",
		alertSupabaseSyncError: "同期失敗:",
		syncStatus: "接続ステータス",
		labelSyncMode: "同期モード",
		syncModeNone: "無効",
		syncModeSupabase: "Supabase (クラウド)",
		syncModeWebDAV: "WebDAV (推奨)",
		labelLastSync: "最終同期",
		labelNever: "未同期",
		labelSyncing: "同期中...",
		alertAlreadyUpToDate: "最新の状態です",
		alertSyncLocked: "他のタブで同期が実行中です",
		alertSyncLockActive: "同期処理中、お待ちください",
		labelSyncInterval: "自動同期の間隔",
		syncInterval0: "リアルタイム",
		syncInterval2: "2分ごと",
		syncInterval5: "5分ごと (推奨)",
		syncInterval10: "10分ごと",
		syncInterval30: "30分ごと",
		syncIntervalManual: "手動のみ",
		labelAuthEmail: "メールアドレス",
		labelAuthPass: "パスワード",
		btnConnectAndSync: "ログインして同期",
		btnPullSync: "クラウドから強制取得",
		btnLogout: "ログアウト",
		alertLoginRequired: "ログイン情報を入力してください",
		alertSbUrlRequired: "Supabase 設定が不足しています",
		alertSyncAccountConnected: "アカウントに接続しました",
		alertPushAllQuery: "ローカルデータをクラウドに上書きしますか?",
		alertPullAllQuery: "クラウドデータでローカルを上書きしますか?",
		labelSupabaseSync: "Supabase 同期",
		labelSupabaseUrl: "API URL",
		labelSupabaseKey: "API Key (anon)",
		aboutDescription: "FC2PPVDB 等のサイトにマグネット検索、高画質カバー、ギャラリーモード、同期機能を提供します。",
		aboutHelpTitle: "主な機能",
		aboutHelpContent: "Sukebei マグネット検索の統合とフィルタリング。\nFourhoi 高画質カバーへの自動置換、キーボード操作対応のギャラリー。\nWebDAV / Supabase 経由のマルチデバイス履歴同期。\nクイックボタン、品番コピー、外部サイト連携。",
		aboutFooterTagline: "効率とプライバシーのために設計。",
		labelTechnicalLogs: "システムログ",
		btnCopyAll: "すべてコピー",
		btnSelectAll: "すべて選択",
		btnDeselectAll: "選択解除",
		btnClearLogs: "ログをクリア",
		alertLogsCopied: "ログをクリップボードにコピーしました",
		alertLogsCleared: "ログを消去しました",
		labelLogFilters: "フィルタ",
		btnCancel: "キャンセル",
		btnSave: "保存",
		btnMoreOptions: "詳細設定",
		btnClose: "閉じる",
		btnBackToTop: "トップへ",
		labelDebugMode: "デバッグモード",
		statusDebugOn: "デバッグ有効",
		statusDebugOff: "通常モード",
		alertDebugOn: "デバッグを有効にしました",
		alertDebugOff: "通常モードに戻りました",
		btnCopyEnv: "診断情報をコピー",
		tooltipCopyId: "品番をコピー",
		dmcaContent: "このスクリプトはローカル拡張ツールであり、<b>動画や画像ファイルをホストまたは保存しません</b>。すべての内容は第三者の公開サイトから取得されています。使用者は自己の責任において利用するものとします。",
		labelDisclaimer: "免责事项",
		labelGreasyFork: "Greasy Fork ページ",
		labelLoading: "読み込み中...",
		labelPreview: "ギャラリー",
		labelPlayPreview: "プレビュー再生",
		alertPreviewLoadFailed: "読み込み失敗",
		toastNetworkRetrying: "ネットワークエラー。リトライ中 ({attempt}/{total})...",
		alertLoggedOut: "ログアウトしました",
		mainPreview: "機能デモ",
		gallerySearch: "画像検索",
		gallerySlideshow: "スライドショー",
		searchPlaceholder: "リスト内を検索...",
		labelError: "エラー",
		confirmDestructiveAction: "警告:この操作は取り消せません。続行しますか?",
		groupScraperDiagnosis: "スクレイパー診断",
		btnTestScrapers: "診断を開始",
		scraperStatusPending: "未テスト",
		scraperStatusOk: "正常",
		scraperStatusError: "接続エラー",
		scraperStatusCF: "CF 検証が必要",
		scraperStatus429: "レート制限",
		scraperStatusLoading: "テスト中...",
		scraperTargetSukebei: "Sukebei (マグネット)",
		scraperTargetFd2: "FD2PPV (出演者)",
		scraperTargetPaipancon: "Paipancon (ギャラリー)",
		alertScraperBlocked: "ブロックされました: {target}",
		btnGoVerify: "検証に行く",
		settingsAdvancedSection: "詳細オプション",
		syncLatestTitle: "最新の同期",
		syncLatestEmpty: "同期記録なし。",
		syncCompletedAt: "完了時刻",
		syncNoConflictYet: "コンフリクトはありません。",
		syncConflictLocalNewer: "ローカルが新しい: {count}",
		syncConflictRemoteNewer: "クラウドが新しい: {count}",
		syncConflictMerged: "マージ済み: {count}",
		debugTraceIdPlaceholder: "Trace ID でフィルタ",
		debugExportJson: "JSON 出力",
		debugPayload: "ペイロード",
		perfSyncExecute: "同期実行",
		perfUpdatedAt: "更新時刻",
		syncActionDebugLogs: "デバッグログ",
		backupImportPreviewPlaceholder: "プレビューがここに表示されます。",
		backupExportSuccessPlain: "エクスポート完了。",
		backupImportErrorInvalidJson: "無効な JSON です。",
		backupImportErrorInvalidPayload: "無効なバックアップ構造です。",
		backupImportErrorUnsupportedVersion: "バージョンが新しすぎるため読み込めません。",
		backupImportErrorReadFailed: "読み込みに失敗しました。",
		backupImportErrorApplyFailed: "データの適用に失敗しました。",
		backupImportConfirm: "このバックアップを復元しますか?",
		backupSettingsAdded: "新規設定: {count}",
		backupSettingsChanged: "変更設定: {count}",
		backupHistoryRows: "履歴: {count}",
		backupHistoryRowsWithCurrent: "履歴: {count} (現在: {current})",
		backupChangedKeys: "変更キー: {keys}",
		syncSummaryFailed: "同期失敗",
		syncSummaryFailedWithMessage: "失敗: {message}",
		syncSummaryCompleted: "同期完了",
		syncSummaryMode: "モード: {mode}",
		syncSummaryPushed: "アップロード {count}",
		syncSummaryPulled: "ダウンロード {count}",
		syncSummaryDirty: "未同期 {count}",
		syncSummaryUpToDate: "最新の状態です。",
		syncSummaryConflictRetry: "衝突を検出し、新しい状態で再試行しました。",
		syncConflictWebdavRetry: "WebDAV ETag 衝突。リフレッシュ後に再試行しました。",
		syncErrorSupabase: "Supabase 同期エラー",
		syncErrorWebdav: "WebDAV 同期エラー",
		syncErrorNoProvider: "プロバイダー未設定",
		syncErrorLocked: "他のタブで同期が進行中です",
		settingsTaskSync: "同期タスク",
		settingsTaskConfigure: "設定",
		settingsTaskDiagnose: "診断",
		settingsTaskInfo: "情報",
		labelSelectionMode: "一括操作モード",
		batchActionMarkViewed: "既読にする",
		batchActionMarkUnviewed: "未読にする",
		batchActionCopyIds: "IDをコピー",
		batchActionExtractMagnets: "マグネットを抽出",
		batchSelectionCount: "{count}個を選択中",
		batchActionConfirm: "実行",
		batchActionCancel: "キャンセル",
		batchExtractTitle: "マグネットリンクの一括抽出",
		batchExtractPlaceholder: "マグネットリンクを取得中...",
		batchExtractProgress: "マグネット取得中 {current}/{total}...",
		batchExtractPartial: "成功 {ok} 件、失敗/なし {fail} 件",
		batchNoneSelected: "先に項目を選択してください",
		batchExtractEmpty: "マグネットリンクが見つかりません",
		batchExtractSuccess: "{count}個のマグネットリンクを抽出しました"
	}
};
/** Optional reader injected from composition root after State is ready. */
var localeReader = null;
var setLocaleReader = (reader) => {
	localeReader = reader;
};
var getActiveLocale = () => {
	const language = localeReader?.() ?? "auto";
	return resolveLocalePreference(String(language || "auto"), navigator.language);
};
var Localization = {
	_translations: baseTranslations,
	get locale() {
		return getActiveLocale();
	},
	t(key, params) {
		const locale = getActiveLocale();
		let value = (this._translations[locale] || this._translations["en-US"])?.[key] || this._translations["en-US"]?.[key] || key;
		if (params && typeof value === "string") Object.entries(params).forEach(([paramKey, paramValue]) => {
			value = value.replaceAll(`{${paramKey}}`, String(paramValue));
		});
		return value;
	},
	resolvePath(obj, path) {
		try {
			return path.split(".").reduce((prev, curr) => {
				if (prev && typeof prev === "object") return prev[curr];
			}, obj);
		} catch {
			return null;
		}
	},
	register(locale, newTranslations) {
		if (!this._translations[locale]) this._translations[locale] = {};
		const target = this._translations[locale];
		if (target) this.deepMerge(target, newTranslations);
	},
	deepMerge(target, source) {
		for (const key in source) if (source[key] instanceof Object && key in target && target[key] instanceof Object) this.deepMerge(target[key], source[key]);
		else target[key] = source[key];
		return target;
	}
};
var t = exports("t",(key, params) => Localization.t(key, params));
var SVG_NS = "http://www.w3.org/2000/svg";
/** Tags allowed in sanitized HTML (localized content + static inline SVG icons). */
var ALLOWED_TAGS = /* @__PURE__ */ new Set([
	"a",
	"b",
	"br",
	"code",
	"div",
	"em",
	"i",
	"li",
	"ol",
	"p",
	"small",
	"span",
	"strong",
	"ul",
	"svg",
	"path",
	"g",
	"circle",
	"rect",
	"line",
	"polyline",
	"polygon",
	"ellipse",
	"defs",
	"title",
	"use",
	"text"
]);
/** Attributes copied verbatim from the source element. */
var ALLOWED_ATTRS = /* @__PURE__ */ new Set([
	"class",
	"id",
	"title",
	"alt",
	"rel",
	"target",
	"role",
	"aria-label",
	"xmlns",
	"viewbox",
	"d",
	"fill",
	"stroke",
	"stroke-width",
	"stroke-linecap",
	"stroke-linejoin",
	"fill-rule",
	"clip-rule",
	"width",
	"height",
	"x",
	"y",
	"cx",
	"cy",
	"r",
	"rx",
	"ry",
	"points",
	"transform",
	"opacity",
	"version"
]);
/** URL-valued attributes that must pass the protocol allowlist. */
var URL_ATTRS = /* @__PURE__ */ new Set([
	"href",
	"src",
	"xlink:href"
]);
var SAFE_URL_PROTOCOLS = /* @__PURE__ */ new Set([
	"http:",
	"https:",
	"mailto:"
]);
var isSafeUrl = (value) => {
	const trimmed = value.trim();
	if (!trimmed) return true;
	try {
		return SAFE_URL_PROTOCOLS.has(new URL(trimmed, "https://local.invalid").protocol);
	} catch {
		return false;
	}
};
var sanitizeNode = (source, output, stats) => {
	for (const child of Array.from(source.childNodes)) {
		if (child.nodeType === Node.TEXT_NODE) {
			output.appendChild(document.createTextNode(child.textContent ?? ""));
			continue;
		}
		if (child.nodeType !== Node.ELEMENT_NODE) continue;
		const element = child;
		const tag = element.localName.toLowerCase();
		if (!ALLOWED_TAGS.has(tag)) {
			stats.droppedElements += 1;
			continue;
		}
		const created = element.namespaceURI === SVG_NS ? document.createElementNS(SVG_NS, tag) : document.createElement(tag);
		for (const attr of Array.from(element.attributes)) {
			const name = attr.name;
			const lower = name.toLowerCase();
			if (lower.startsWith("on")) {
				stats.droppedAttributes += 1;
				continue;
			}
			if (lower === "style") {
				stats.droppedAttributes += 1;
				continue;
			}
			if (URL_ATTRS.has(lower)) {
				if (!isSafeUrl(attr.value)) {
					stats.droppedAttributes += 1;
					continue;
				}
			} else if (!ALLOWED_ATTRS.has(lower)) {
				stats.droppedAttributes += 1;
				continue;
			}
			created.setAttribute(name, attr.value);
		}
		sanitizeNode(element, created, stats);
		output.appendChild(created);
	}
};
/**
* Sanitizes untrusted HTML against script execution, event handlers, and unsafe URLs.
* Returns both the sanitized markup and what was stripped, so callers can distinguish
* "re-serialized verbatim" from "unsafe content actually removed".
*/
var sanitizeHtmlDetailed = (raw) => {
	const template = document.createElement("template");
	template.innerHTML = raw;
	const output = document.createElement("div");
	const stats = {
		droppedElements: 0,
		droppedAttributes: 0
	};
	sanitizeNode(template.content, output, stats);
	return {
		html: output.innerHTML,
		droppedElements: stats.droppedElements,
		droppedAttributes: stats.droppedAttributes
	};
};
var h = exports("h",(tag, props = {}, ...children) => {
	const el = document.createElement(tag);
	for (const [key, val] of Object.entries(props)) if (key === "className") el.className = val;
	else if (key === "style" && typeof val === "object" && val !== null) Object.entries(val).forEach(([sKey, sVal]) => {
		if (sKey.startsWith("--")) el.style.setProperty(sKey, String(sVal));
		else el.style[sKey] = String(sVal);
	});
	else if (key === "dataset" && typeof val === "object" && val !== null) Object.assign(el.dataset, val);
	else if (key.startsWith("on") && typeof val === "function") {
		const eventName = key.toLowerCase().substring(2);
		el.addEventListener(eventName, val);
	} else if (key === "innerHTML") {
		const raw = String(val);
		if (tag === "style") el.textContent = raw;
		else {
			const { html, droppedElements, droppedAttributes } = sanitizeHtmlDetailed(raw);
			if (droppedElements > 0 || droppedAttributes > 0) Logger.warn("DOM", `Sanitized unsafe innerHTML (removed ${droppedElements} element(s), ${droppedAttributes} attribute(s))`);
			el.innerHTML = html;
		}
	} else if (val !== null && val !== void 0 && val !== false) {
		const target = el;
		if (key in target) target[key] = val;
		else el.setAttribute(key, String(val));
	}
	children.flat().forEach((child) => {
		if (child === null || child === void 0 || child === false) return;
		el.appendChild(child instanceof Node ? child : document.createTextNode(String(child)));
	});
	return el;
});
var getBestImageSource = (img) => {
	if (!img) return "";
	const d = img.dataset;
	const src = d.original || d.src || d.lazySrc || img.src || "";
	if (src.startsWith("data:image") || src.includes("pixel.gif")) return d.original || d.src || d.lazySrc || "";
	return src;
};
/**
* Service tokens ?typed symbols for pluggable service resolution.
*
* Each token maps to a service interface. Consumers resolve by token,
* never by concrete class import. This enables:
* - Swappable implementations (test mocks, alternative providers)
* - No compile-time coupling between service modules
* - Clean dependency declaration via resolve() calls
*/
var HISTORY_SERVICE = exports("H",createToken("HistoryService"));
var SCRAPER_SERVICE = exports("p",createToken("ScraperService"));
var MAGNET_SERVICE = createToken("MagnetService");
var PREVIEW_SERVICE = createToken("PreviewService");
var PORTAL_SERVICE = exports("P",createToken("PortalService"));
var SYNC_SERVICE = exports("e",createToken("SyncService"));
var BACKUP_SERVICE = exports("B",createToken("BackupService"));
var SELECTION_SERVICE = createToken("SelectionService");
var MENU_SERVICE = createToken("MenuService");
var MESSAGING_SERVICE = createToken("MessagingService");
var TIMELINE_SERVICE = createToken("TimelineService");
var CLEANUP_SERVICE = createToken("CleanupService");
var GRID_SERVICE = createToken("GridService");
var SITE_SERVICE = createToken("SiteService");
var STYLE_SERVICE = createToken("StyleService");
var STATE_SERVICE = exports("S",createToken("StateService"));
var DATA_SERVICE = exports("D",createToken("DataService"));
var UI_MANAGER = createToken("UIManager");
var QUICKBAR_SERVICE = createToken("QuickBar");
var WEBDAV_PROVIDER = createToken("WebDAVProvider");
var SUPABASE_PROVIDER = createToken("SupabaseProvider");
var CARD_ENHANCEMENT_SERVICE = createToken("CardEnhancementService");
var log$52 = Logger.scope("UIHost");
var OverlayStackImpl = class {
	stack = [];
	handleKeyDown = (e) => {
		if (e.key === "Escape") {
			const top = this.stack[this.stack.length - 1];
			if (top) {
				e.preventDefault();
				e.stopPropagation();
				top.close();
			}
		}
	};
	push(overlay) {
		if (this.stack.length === 0) document.addEventListener("keydown", this.handleKeyDown, true);
		if (!this.stack.includes(overlay)) this.stack.push(overlay);
	}
	remove(overlay) {
		const index = this.stack.indexOf(overlay);
		if (index > -1) this.stack.splice(index, 1);
		if (this.stack.length === 0) document.removeEventListener("keydown", this.handleKeyDown, true);
	}
};
var OverlayStack = new OverlayStackImpl();
var UIHostImpl = class {
	host = null;
	_shadow = null;
	styleSheet = null;
	resetHostState() {
		this.host = null;
		this._shadow = null;
		this.styleSheet = null;
	}
	ensureConnectedHost() {
		if (!this.host?.isConnected || !this._shadow) {
			this.resetHostState();
			this.onInit();
		}
	}
	onInit() {
		if (this.host && this.host.isConnected) return;
		log$52.debug("Initializing shadow host");
		this.host = h("div", {
			id: DOM_IDS.UI_HOST,
			style: {
				position: "fixed",
				inset: "0",
				pointerEvents: "none",
				zIndex: "2147483647"
			}
		});
		this._shadow = this.host.attachShadow({ mode: "open" });
		const css = resolveService(STYLE_SERVICE).getCss();
		try {
			if ("adoptedStyleSheets" in this._shadow) {
				this.styleSheet = new CSSStyleSheet();
				this.styleSheet.replaceSync(css);
				this._shadow.adoptedStyleSheets = [this.styleSheet];
			} else throw new Error("Not supported");
		} catch {
			const style = h("style", { innerHTML: css });
			this._shadow.appendChild(style);
		}
		document.body.appendChild(this.host);
	}
	get shadow() {
		this.ensureConnectedHost();
		return this._shadow;
	}
	add(el) {
		this.ensureConnectedHost();
		this._shadow.appendChild(el);
	}
};
var UIHost = new UIHostImpl();
var log$51 = Logger.scope("Browser");
var BrowserService = class {
	/**
	* Opens a URL in a new tab using GM_openInTab if available, fallback to window.open.
	*/
	static openTab(url, active = true) {
		if (typeof GM_openInTab !== "undefined") try {
			GM_openInTab(url, {
				active,
				insert: true,
				setParent: true
			});
			return;
		} catch (error) {
			log$51.error("Failed to open tab via GM_openInTab", error);
		}
		window.open(url, "_blank", "noopener,noreferrer");
	}
	/**
	* Shows a system-level notification using GM_notification.
	*/
	static notify(text, title = "FC2PPVDB Enhanced", image, onClick) {
		if (typeof GM_notification !== "undefined") try {
			GM_notification({
				text,
				title,
				image: image || "data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\"/>",
				timeout: 5e3,
				onclick: onClick
			});
		} catch (error) {
			log$51.error("Failed to show system notification", error);
		}
	}
};
var IconArrowUp = "<svg viewBox=\"0 0 384 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.3l105.4 105.3c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z\"/></svg>";
var IconGear = exports("x","<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M495.9 166.6c3.2 8.7.5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4l-55.6 17.8c-8.8 2.8-18.6.3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4c-1.1-8.4-1.7-16.9-1.7-25.5s.6-17.1 1.7-25.4l-43.3-39.4c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160a80 80 0 1 0 0 160\"/></svg>");
var IconBan = "<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M367.2 412.5L99.5 144.8C77.1 176.1 64 214.5 64 256c0 106 86 192 192 192c41.5 0 79.9-13.1 111.2-35.5m45.3-45.3C434.9 335.9 448 297.5 448 256c0-106-86-192-192-192c-41.5 0-79.9 13.1-111.2 35.5zM0 256a256 256 0 1 1 512 0a256 256 0 1 1-512 0\"/></svg>";
var IconMagnet = "<svg viewBox=\"0 0 448 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M0 160v96c0 123.7 100.3 224 224 224s224-100.3 224-224v-96H320v96c0 53-43 96-96 96s-96-43-96-96v-96zm0-32h128V64c0-17.7-14.3-32-32-32H32C14.3 32 0 46.3 0 64zm320 0h128V64c0-17.7-14.3-32-32-32h-64c-17.7 0-32 14.3-32 32z\"/></svg>";
var IconEyeSlash = exports("K","<svg viewBox=\"0 0 640 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2s-6.3 25.5 4.1 33.7l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7l-105.2-82.4c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8zm184.3 144.4c25.5-23.3 59.6-37.5 96.9-37.5c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c8.4-19.3 10.6-41.4 4.8-63.3c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zM373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9.5-13.6 1.4-20.2l-94.3-74.3c-22.8 29.7-39.1 59.3-48.6 82.2c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1c47 43.8 111.7 80.6 192.5 80.6c47.8 0 89.9-12.9 126.2-32.5z\"/></svg>");
var IconRotate = exports("n","<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M142.9 142.9c-17.5 17.5-30.1 38-37.8 59.8c-5.9 16.7-24.2 25.4-40.8 19.5S38.9 198 44.8 181.4c10.8-30.7 28.4-59.4 52.8-83.8c87.2-87.2 228.3-87.5 315.8-1L455 55c6.9-6.9 17.2-8.9 26.2-5.2S496 62.3 496 72v128c0 13.3-10.7 24-24 24H344c-9.7 0-18.5-5.8-22.2-14.8s-1.7-19.3 5.2-26.2l41.1-41.1c-62.6-61.5-163.1-61.2-225.3 1zM16 312c0-13.3 10.7-24 24-24h128c9.7 0 18.5 5.8 22.2 14.8s1.7 19.3-5.2 26.2l-41.1 41.1c62.6 61.5 163.1 61.2 225.3-1c17.5-17.5 30.1-38 37.8-59.8c5.9-16.7 24.2-25.4 40.8-19.5s25.4 24.2 19.5 40.8c-10.8 30.6-28.4 59.3-52.9 83.8c-87.2 87.2-228.3 87.5-315.8 1L57 457c-6.9 6.9-17.2 8.9-26.2 5.2S16 449.7 16 440V312.1z\"/></svg>");
var IconPlus = "<svg viewBox=\"0 0 448 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32v144H48c-17.7 0-32 14.3-32 32s14.3 32 32 32h144v144c0 17.7 14.3 32 32 32s32-14.3 32-32V288h144c17.7 0 32-14.3 32-32s-14.3-32-32-32H256z\"/></svg>";
var IconImages = "<svg viewBox=\"0 0 576 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M160 32c-35.3 0-64 28.7-64 64v224c0 35.3 28.7 64 64 64h352c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64zm236 106.7l96 144c4.9 7.4 5.4 16.8 1.2 24.6S480.9 320 472 320H200c-9.2 0-17.6-5.3-21.6-13.6s-2.9-18.2 2.9-25.4l64-80c4.6-5.7 11.4-9 18.7-9s14.2 3.3 18.7 9l17.3 21.6l56-84c4.5-6.6 12-10.6 20-10.6s15.5 4 20 10.7M192 128a32 32 0 1 1 64 0a32 32 0 1 1-64 0m-144-8c0-13.3-10.7-24-24-24S0 106.7 0 120v224c0 75.1 60.9 136 136 136h320c13.3 0 24-10.7 24-24s-10.7-24-24-24H136c-48.6 0-88-39.4-88-88z\"/></svg>";
var IconSpinner = "<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M304 48a48 48 0 1 0-96 0a48 48 0 1 0 96 0m0 416a48 48 0 1 0-96 0a48 48 0 1 0 96 0M48 304a48 48 0 1 0 0-96a48 48 0 1 0 0 96m464-48a48 48 0 1 0-96 0a48 48 0 1 0 96 0M142.9 437A48 48 0 1 0 75 369.1a48 48 0 1 0 67.9 67.9m0-294.2A48 48 0 1 0 75 75a48 48 0 1 0 67.9 67.9zM369.1 437a48 48 0 1 0 67.9-67.9a48 48 0 1 0-67.9 67.9\"/></svg>";
var IconCircleCheck = "<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M256 512a256 256 0 1 0 0-512a256 256 0 1 0 0 512m113-303L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z\"/></svg>";
var IconCircleXmark = "<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M256 512a256 256 0 1 0 0-512a256 256 0 1 0 0 512m-81-337c9.4-9.4 24.6-9.4 33.9 0l47 47l47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47l47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47l-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47l-47-47c-9.4-9.4-9.4-24.6 0-33.9\"/></svg>";
var IconTriangleExclamation = exports("F","<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7.2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8.2-40.1l216-368C228.7 39.5 241.8 32 256 32m0 128c-13.3 0-24 10.7-24 24v112c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24m32 224a32 32 0 1 0-64 0a32 32 0 1 0 64 0\"/></svg>");
var IconCircleInfo = exports("G","<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M256 512a256 256 0 1 0 0-512a256 256 0 1 0 0 512m-40-176h24v-64h-24c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24h-80c-13.3 0-24-10.7-24-24s10.7-24 24-24m40-208a32 32 0 1 1 0 64a32 32 0 1 1 0-64\"/></svg>");
var IconXmark = "<svg viewBox=\"0 0 384 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7L86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256L41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3l105.4 105.3c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256z\"/></svg>";
var IconSliders = exports("b","<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M0 416c0 17.7 14.3 32 32 32h54.7c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H233.3c-12.3-28.3-40.5-48-73.3-48s-61 19.7-73.3 48H32c-17.7 0-32 14.3-32 32m128 0a32 32 0 1 1 64 0a32 32 0 1 1-64 0m192-160a32 32 0 1 1 64 0a32 32 0 1 1-64 0m32-80c-32.8 0-61 19.7-73.3 48H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h246.7c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48H480c17.7 0 32-14.3 32-32s-14.3-32-32-32h-54.7c-12.3-28.3-40.5-48-73.3-48m-160-48a32 32 0 1 1 0-64a32 32 0 1 1 0 64m73.3-64C253 35.7 224.8 16 192 16s-61 19.7-73.3 48H32C14.3 64 0 78.3 0 96s14.3 32 32 32h86.7c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48H480c17.7 0 32-14.3 32-32s-14.3-32-32-32z\"/></svg>");
var IconDatabase = exports("l","<svg viewBox=\"0 0 448 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M448 80v48c0 44.2-100.3 80-224 80S0 172.2 0 128V80C0 35.8 100.3 0 224 0s224 35.8 224 80m-54.8 134.7c20.8-7.4 39.9-16.9 54.8-28.6V288c0 44.2-100.3 80-224 80S0 332.2 0 288V186.1c14.9 11.8 34 21.2 54.8 28.6C99.7 230.7 159.5 240 224 240s124.3-9.3 169.2-25.3M0 346.1c14.9 11.8 34 21.2 54.8 28.6C99.7 390.7 159.5 400 224 400s124.3-9.3 169.2-25.3c20.8-7.4 39.9-16.9 54.8-28.6V432c0 44.2-100.3 80-224 80S0 476.2 0 432z\"/></svg>");
var IconFilter = exports("v","<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M3.9 54.9C10.5 40.9 24.5 32 40 32h432c15.5 0 29.5 8.9 36.1 22.9s4.6 30.5-5.2 42.5L320 320.9V448c0 12.1-6.8 23.2-17.7 28.6s-23.8 4.3-33.5-3l-64-48c-8.1-6-12.8-15.5-12.8-25.6v-79.1L9 97.3C-.7 85.4-2.8 68.8 3.9 54.9\"/></svg>");
var IconPalette = exports("u","<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M512 256v2.7c-.4 36.5-33.6 61.3-70.1 61.3H344c-26.5 0-48 21.5-48 48c0 3.4.4 6.7 1 9.9c2.1 10.2 6.5 20 10.8 29.9c6.1 13.8 12.1 27.5 12.1 42c0 31.8-21.6 60.7-53.4 62c-3.5.1-7 .2-10.6.2C114.6 512 0 397.4 0 256S114.6 0 256 0s256 114.6 256 256m-384 32a32 32 0 1 0-64 0a32 32 0 1 0 64 0m0-96a32 32 0 1 0 0-64a32 32 0 1 0 0 64m160-96a32 32 0 1 0-64 0a32 32 0 1 0 64 0m96 96a32 32 0 1 0 0-64a32 32 0 1 0 0 64\"/></svg>");
var IconClockRotateLeft = exports("w","<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M75 75L41 41C25.9 25.9 0 36.6 0 57.9V168c0 13.3 10.7 24 24 24h110.1c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75m181 53c-13.3 0-24 10.7-24 24v104c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65V152c0-13.3-10.7-24-24-24z\"/></svg>");
var IconFileExport = exports("a","<svg viewBox=\"0 0 576 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M0 64C0 28.7 28.7 0 64 0h160v128c0 17.7 14.3 32 32 32h128v128H216c-13.3 0-24 10.7-24 24s10.7 24 24 24h168v112c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64zm384 272v-48h110.1l-39-39c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l80 80c9.4 9.4 9.4 24.6 0 33.9l-80 80c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l39-39zm0-208H256V0z\"/></svg>");
var IconFileImport = exports("I","<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M128 64c0-35.3 28.7-64 64-64h160v128c0 17.7 14.3 32 32 32h128v288c0 35.3-28.7 64-64 64H192c-35.3 0-64-28.7-64-64V336h174.1l-39 39c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l80-80c9.4-9.4 9.4-24.6 0-33.9l-80-80c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l39 39l-174.1.1zm0 224v48H24c-13.3 0-24-10.7-24-24s10.7-24 24-24zm384-160H384V0z\"/></svg>");
var IconChevronLeft = "<svg viewBox=\"0 0 320 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256L246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z\"/></svg>";
var IconChevronRight = "<svg viewBox=\"0 0 320 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256L73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z\"/></svg>";
var IconEye = exports("J","<svg viewBox=\"0 0 576 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32M144 256a144 144 0 1 1 288 0a144 144 0 1 1-288 0m144-64c0 35.3-28.7 64-64 64c-7.1 0-13.9-1.2-20.3-3.3c-5.5-1.8-11.9 1.6-11.7 7.4c.3 6.9 1.3 13.8 3.2 20.7c13.7 51.2 66.4 81.6 117.6 67.9s81.6-66.4 67.9-117.6c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3\"/></svg>");
var IconLink = exports("f","<svg viewBox=\"0 0 640 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5l112.2-112.3c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0z\"/></svg>");
var IconBolt = "<svg viewBox=\"0 0 448 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M349.4 44.6c5.9-13.7 1.5-29.7-10.6-38.5s-28.6-8-39.9 1.8l-256 224c-10 8.8-13.6 22.9-8.9 35.3S50.7 288 64 288h111.5L98.6 467.4c-5.9 13.7-1.5 29.7 10.6 38.5s28.6 8 39.9-1.8l256-224c10-8.8 13.6-22.9 8.9-35.3s-16.6-20.7-30-20.7H272.5z\"/></svg>";
var IconPlayCircle = "<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M0 256a256 256 0 1 1 512 0a256 256 0 1 1-512 0m188.3-108.9c-7.6 4.2-12.3 12.3-12.3 20.9v176c0 8.7 4.7 16.7 12.3 20.9s16.8 4.1 24.3-.5l144-88c7.1-4.4 11.5-12.1 11.5-20.5s-4.4-16.1-11.5-20.5l-144-88c-7.4-4.5-16.7-4.7-24.3-.5z\"/></svg>";
var IconPlay = exports("d","<svg viewBox=\"0 0 384 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80v352c0 17.4 9.4 33.4 24.5 41.9S58.2 482 73 473l288-176c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41z\"/></svg>");
var IconPause = "<svg viewBox=\"0 0 320 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M48 64C21.5 64 0 85.5 0 112v288c0 26.5 21.5 48 48 48h32c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48zm192 0c-26.5 0-48 21.5-48 48v288c0 26.5 21.5 48 48 48h32c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48z\"/></svg>";
var IconVial = exports("o","<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M342.6 9.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l9.4 9.4L28.1 342.6C10.1 360.6 0 385 0 410.5v5.5c0 53 43 96 96 96h5.5c25.5 0 49.9-10.1 67.9-28.1L448 205.3l9.4 9.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-32-32l-96-96l-32-32zM205.3 256L352 109.3l50.7 50.7l-96 96H205.2z\"/></svg>");
var IconImageSearch = "<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M416 208c0 45.9-14.9 88.3-40 122.7l126.6 126.7c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0s208 93.1 208 208M208 352a144 144 0 1 0 0-288a144 144 0 1 0 0 288\"/></svg>";
var IconListUl = "<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M64 144a48 48 0 1 0 0-96a48 48 0 1 0 0 96m128-80c-17.7 0-32 14.3-32 32s14.3 32 32 32h288c17.7 0 32-14.3 32-32s-14.3-32-32-32zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32h288c17.7 0 32-14.3 32-32s-14.3-32-32-32zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32h288c17.7 0 32-14.3 32-32s-14.3-32-32-32zM64 464a48 48 0 1 0 0-96a48 48 0 1 0 0 96m48-208a48 48 0 1 0-96 0a48 48 0 1 0 96 0\"/></svg>";
var IconServer = exports("m","<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M64 32C28.7 32 0 60.7 0 96v64c0 35.3 28.7 64 64 64h384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64zm280 72a24 24 0 1 1 0 48a24 24 0 1 1 0-48m48 24a24 24 0 1 1 48 0a24 24 0 1 1-48 0M64 288c-35.3 0-64 28.7-64 64v64c0 35.3 28.7 64 64 64h384c35.3 0 64-28.7 64-64v-64c0-35.3-28.7-64-64-64zm280 72a24 24 0 1 1 0 48a24 24 0 1 1 0-48m56 24a24 24 0 1 1 48 0a24 24 0 1 1-48 0\"/></svg>");
var PORTAL_ICONS = {
	dmm: IconPlayCircle,
	fc2: IconLink,
	supjav: IconBolt,
	missav: IconPlayCircle,
	javdb: IconDatabase,
	javbus: IconDatabase,
	javlibrary: IconDatabase,
	fd2ppv: IconServer,
	fc2db: IconDatabase,
	sukebei: "<svg viewBox=\"0 0 512 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M416 208c0 45.9-14.9 88.3-40 122.7l126.6 126.7c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0s208 93.1 208 208M208 352a144 144 0 1 0 0-288a144 144 0 1 0 0 288\"/></svg>",
	fc2cmadb: IconDatabase
};
var log$50 = Logger.scope("Retry");
var isServerErrorStatus = (status) => typeof status === "number" && status >= 500 && status < 600;
var RetryManager = class {
	static defaultConfig = {
		maxRetries: 3,
		backoffMs: [
			1e3,
			3e3,
			5e3
		],
		shouldRetry: (error) => {
			const status = getHttpStatus(error);
			return status === 0 || isServerErrorStatus(status);
		}
	};
	static async executeWithRetry(operation, config = {}) {
		const finalConfig = {
			...this.defaultConfig,
			...config
		};
		let lastError;
		for (let attempt = 0; attempt <= finalConfig.maxRetries; attempt++) try {
			log$50.debug(`Attempt ${attempt + 1}/${finalConfig.maxRetries + 1}`);
			return await operation();
		} catch (error) {
			lastError = error;
			if (attempt === finalConfig.maxRetries) {
				log$50.error("All retries exhausted", error);
				throw error;
			}
			if (finalConfig.shouldRetry && !finalConfig.shouldRetry(error)) {
				log$50.warn("Error not retryable", error);
				throw error;
			}
			if (finalConfig.onRetry) try {
				finalConfig.onRetry(error, attempt + 1);
			} catch (retryError) {
				log$50.warn("Error in onRetry callback", retryError);
			}
			const delay = finalConfig.backoffMs[attempt] ?? finalConfig.backoffMs[finalConfig.backoffMs.length - 1] ?? 0;
			log$50.debug(`Retry in ${delay}ms`, {
				attempt: attempt + 1,
				error
			});
			await new Promise((resolve) => setTimeout(resolve, delay));
		}
		throw lastError;
	}
	/**
	* Standard retry presets (Pure logic only)
	*/
	static presets = {
		network: (onRetry) => ({
			maxRetries: 3,
			backoffMs: [
				1e3,
				3e3,
				5e3
			],
			shouldRetry: (error) => {
				const status = getHttpStatus(error);
				return !!(status === 0 || status === 408 || isServerErrorStatus(status));
			},
			onRetry
		}),
		sync: {
			maxRetries: 2,
			backoffMs: [2e3, 5e3],
			shouldRetry: (error) => {
				const status = getHttpStatus(error);
				return !!(status !== 401 && status !== 403);
			}
		},
		supabaseSync: {
			maxRetries: 2,
			backoffMs: [1e3, 3e3],
			shouldRetry: (error) => {
				const status = getHttpStatus(error);
				return !!(status === 0 || status === 408 || status === 429 || isServerErrorStatus(status));
			}
		},
		magnet: {
			maxRetries: 2,
			backoffMs: [1500, 3e3],
			shouldRetry: (error) => {
				return !!(getHttpStatus(error) !== 429);
			}
		}
	};
};
var MediaUtils = {
	extractFC2Id: (url) => url?.match(/(?:articles\/|fc2-ppv-|fc2ppv-|fc2-|work\/|watch\/|^)(\d{5,8})(?:$|\/|\.|_)/i)?.[1] ?? null,
	parseVideoId: (text, url = "") => {
		const input = (text + " " + url).replace(/[+\s_]/g, "-").toUpperCase();
		const fc2Prefix = input.match(/(?:FC2[^\d]*PPV[^\d]*|FC2-)(\d{5,8})/i);
		if (fc2Prefix && fc2Prefix[1]) {
			const id = fc2Prefix[1];
			return {
				id,
				type: "fc2",
				previewSlug: `fc2-ppv-${id}`
			};
		}
		const fc2FromUrl = MediaUtils.extractFC2Id(url);
		if (fc2FromUrl) return {
			id: fc2FromUrl,
			type: "fc2",
			previewSlug: `fc2-ppv-${fc2FromUrl}`
		};
		const jav = input.match(/([A-Z0-9]{2,20})[^A-Z0-9]*?(\d{2,8}(?:-\d{1,4})?)/);
		if (jav && jav[1] && jav[2]) {
			let prefix = jav[1];
			if (!/^\d+$/.test(prefix)) {
				if (prefix === "DM" && (input.includes("/DM") || input.includes("-DM"))) return null;
				if (prefix === "PONDO" || prefix === "1PONDO") prefix = "1PON";
				if (prefix === "MUSUME" || prefix === "10MUSUME") prefix = "10MU";
				if (prefix === "PACOPACOMAMA") prefix = "PACO";
				if (prefix === "CARIBBEANCOM") prefix = "CARIB";
				if (!JAV_PREFIX_BLACKLIST.includes(prefix)) return {
					id: `${prefix}-${jav[2]}`,
					type: "jav"
				};
			}
		}
		const dateMatch = input.match(/(\d{6,8})-(\d{1,4})/);
		if (dateMatch && dateMatch[1] && dateMatch[2]) {
			const d = dateMatch[1];
			if (d.length === 6 || d.length === 8) return {
				id: `${dateMatch[1]}-${dateMatch[2]}`,
				type: "jav"
			};
		}
		const fc2Raw = input.match(/(?:^|[^A-Z0-9])(\d{5,10})(?:$|[^A-Z0-9])/);
		if (fc2Raw && fc2Raw[1]) {
			const id = fc2Raw[1];
			return {
				id,
				type: "fc2",
				previewSlug: `fc2-ppv-${id}`
			};
		}
		return null;
	},
	cleanActressName: (name) => {
		if (!name) return null;
		const n = name.trim();
		const blacklist = ACTRESS_BLACKLIST;
		if (ACTRESS_BLACKLIST_STRINGS.includes(n) || blacklist.some((reg) => reg.test(n))) return null;
		if (n.length > VALIDATION.ACTRESS_NAME_MAX_LENGTH || n.length < VALIDATION.ACTRESS_NAME_MIN_LENGTH) return null;
		return n;
	},
	formatDate: (dateStr) => {
		if (!dateStr) return "";
		try {
			const date = new Date(dateStr);
			if (isNaN(date.getTime())) return dateStr;
			return date.toLocaleDateString(void 0, {
				year: "numeric",
				month: "2-digit",
				day: "2-digit"
			});
		} catch {
			return dateStr;
		}
	},
	cleanImageUrl: (url) => {
		if (!url) return url;
		return url.replace(/!\d+x\d+\.(jpg|jpeg|png|webp)/gi, "");
	},
	/**
	* Splits a video ID into parts for flexible matching (especially for date-based brands)
	* e.g., "1PONDO-042526-001" -> { brand: "1PONDO", short: "1PON", date: "042526", serial: "001" }
	*/
	parseDateIdParts: (id) => {
		const match = id.toUpperCase().match(/^(1PON|10MU|CARIB|PACO|1PONDO|10MUSUME|CARIBBEANCOM|PACOPACOMAMA)-(\d{6,8})-(\d{1,4})$/);
		if (!match || !match[1] || !match[2] || !match[3]) return null;
		let brand = match[1];
		const date = match[2];
		const serial = match[3];
		if (brand === "1PON") brand = "1PONDO";
		if (brand === "10MU") brand = "10MUSUME";
		if (brand === "CARIB") brand = "CARIBBEANCOM";
		if (brand === "PACO") brand = "PACOPACOMAMA";
		return {
			brand,
			short: {
				"1PONDO": "1PON",
				"10MUSUME": "10MU",
				CARIBBEANCOM: "CARIB",
				PACOPACOMAMA: "PACO"
			}[brand] || brand,
			date,
			serial
		};
	}
};
var Storage = {
	get: (key, def) => typeof GM_getValue !== "undefined" ? GM_getValue(key, def) : def,
	set: (key, val) => {
		if (typeof GM_setValue !== "undefined") GM_setValue(key, val);
	},
	delete: (key) => {
		if (typeof GM_deleteValue !== "undefined") GM_deleteValue(key);
	}
};
/** Convert hex color string to "r, g, b" format for CSS custom properties */
var hexToRgb = (hex) => {
	const h = hex.startsWith("#") ? hex : `#${hex}`;
	const r = parseInt(h.slice(1, 3), 16);
	const g = parseInt(h.slice(3, 5), 16);
	const b = parseInt(h.slice(5, 7), 16);
	if (isNaN(r) || isNaN(g) || isNaN(b)) {
		console.warn(`[hexToRgb] Invalid hex color: "${hex}", falling back to black`);
		return "0, 0, 0";
	}
	return `${r}, ${g}, ${b}`;
};
var Utils = {
	debounce: (func, delay) => {
		let t;
		return (...a) => {
			if (t) clearTimeout(t);
			t = setTimeout(() => func(...a), delay);
		};
	},
	chunk: (arr, size) => Array.from({ length: Math.ceil(arr.length / size) }, (_, i) => arr.slice(i * size, i * size + size)),
	sleep: (ms) => new Promise((res) => setTimeout(res, ms)),
	copyToClipboard: async (text) => {
		if (typeof GM_setClipboard !== "undefined") {
			GM_setClipboard(text);
			return;
		}
		if (typeof navigator !== "undefined" && navigator.clipboard) await navigator.clipboard.writeText(text);
	}
};
var log$49 = Logger.scope("Device");
var DeviceService = class {
	static _platform = null;
	static get platform() {
		if (!this._platform) this._platform = this.detectPlatform();
		return this._platform;
	}
	static detectPlatform() {
		let os = "unknown";
		let browser = "unknown";
		const info = typeof GM_info !== "undefined" ? GM_info : null;
		if (info?.platform) {
			const p = info.platform;
			os = p.os || "unknown";
			browser = p.browserName || "unknown";
		}
		const ua = navigator.userAgent.toLowerCase();
		if (os === "unknown") {
			if (/android/.test(ua)) os = "android";
			else if (/iphone|ipad|ipod/.test(ua)) os = "ios";
			else if (/windows/.test(ua)) os = "windows";
			else if (/macintosh/.test(ua)) os = "macos";
			else if (/linux/.test(ua)) os = "linux";
		}
		const isTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0 || window.matchMedia("(pointer: coarse)").matches;
		const isSmallScreen = window.matchMedia("(max-width: 768px)").matches;
		const isMobile = os === "android" || os === "ios" || isSmallScreen && isTouch;
		log$49.debug(`[Precision Detect] OS: ${os}, Small: ${isSmallScreen}, Touch: ${isTouch} -> MobileMode: ${isMobile}`);
		return {
			os,
			browser,
			isMobile,
			isTouch,
			isSmallScreen
		};
	}
	/**
	* Should use mobile-optimized UI (Touch friendly, stacked layout)
	*/
	static isMobile() {
		return this.platform.isMobile;
	}
	/**
	* Specifically identifies phone-sized devices
	*/
	static isPhone() {
		return this.isMobileOS() && this.platform.isSmallScreen;
	}
	/**
	* Specifically identifies tablets (Mobile OS but large screen)
	*/
	static isTablet() {
		return this.isMobileOS() && !this.platform.isSmallScreen;
	}
	/**
	* True desktop (Desktop OS and not forced into a small window)
	*/
	static isDesktop() {
		return !this.isMobileOS() && !this.platform.isSmallScreen;
	}
	static isTouch() {
		return this.platform.isTouch;
	}
	static isMobileOS() {
		return this.platform.os === "android" || this.platform.os === "ios";
	}
};
var log$48 = Logger.scope("Gzip");
var GzipServiceImpl = class {
	_isSupported = null;
	isSupported() {
		if (this._isSupported !== null) return this._isSupported;
		try {
			this._isSupported = typeof window.CompressionStream === "function" && typeof window.DecompressionStream === "function";
		} catch {
			this._isSupported = false;
		}
		return this._isSupported;
	}
	async compress(data) {
		if (!this.isSupported()) return data instanceof Blob ? data : new Blob([data], { type: "application/json" });
		try {
			const compressedStream = (data instanceof Blob ? data.stream() : new Blob([data], { type: "application/json" }).stream()).pipeThrough(new CompressionStream("gzip"));
			return await new Response(compressedStream).blob();
		} catch (error) {
			log$48.error("Compression error", error);
			return data instanceof Blob ? data : new Blob([data], { type: "application/json" });
		}
	}
	async decompress(data) {
		if (!this.isSupported()) return await data.text();
		try {
			if (!await this.checkIfGzip(data)) return await data.text();
			const decompressedStream = data.stream().pipeThrough(new DecompressionStream("gzip"));
			return await new Response(decompressedStream).text();
		} catch (error) {
			log$48.error("Decompression error", error);
			return await data.text();
		}
	}
	async checkIfGzip(blob) {
		if (blob.size < 2) return false;
		const arrayBuffer = await blob.slice(0, 2).arrayBuffer();
		const header = new Uint8Array(arrayBuffer);
		return header[0] === 31 && header[1] === 139;
	}
};
var GzipService = new GzipServiceImpl();
var UIUtils = exports("U",{
	h,
	icon: (svgContent, className = "") => {
		return h("span", {
			className: `fc2-icon ${className}`.trim(),
			innerHTML: svgContent
		});
	},
	copyButtonBehavior: (btn, textToCopy, i18nCopied) => {
		if (btn.dataset.copied === "true") return;
		Utils.copyToClipboard(textToCopy);
		btn.dataset.copied = "true";
		const tt = btn.querySelector(`.${Config.CLASSES.tooltip}`);
		if (tt) {
			const originalTip = tt.textContent;
			tt.textContent = i18nCopied;
			setTimeout(() => {
				tt.textContent = originalTip;
				btn.dataset.copied = "false";
			}, Config.COPIED_BADGE_DURATION);
		} else {
			const originalText = btn.textContent;
			btn.textContent = i18nCopied;
			setTimeout(() => {
				btn.textContent = originalText;
				btn.dataset.copied = "false";
			}, Config.COPIED_BADGE_DURATION);
		}
	},
	toggleLoading: (cont, show, btnCreator) => {
		if (!cont?.isConnected) return;
		const spinner = cont.querySelector(`.${Config.CLASSES.btnLoading}`);
		if (show) {
			if (!spinner) {
				const btn = btnCreator(IconSpinner, t("labelLoading"));
				btn.classList.add(Config.CLASSES.btnLoading);
				cont.appendChild(btn);
			}
			cont.classList.add("fc2-skeleton");
		} else {
			if (spinner) spinner.remove();
			cont.classList.remove("fc2-skeleton");
		}
	}
});
var log$47 = Logger.scope("Toast");
var MAX_TOASTS = 8;
var resolveToastDisplayOptions = (options) => ({
	duration: options.duration === 0 ? 0 : options.duration || TIMING.TOAST_DEFAULT_DURATION,
	showClose: options.showClose ?? true
});
var getToastIconSvg = (type) => {
	switch (type) {
		case "success": return IconCircleCheck;
		case "error": return IconCircleXmark;
		case "warn": return IconTriangleExclamation;
		default: return IconCircleInfo;
	}
};
var getToastColor = (type) => {
	switch (type) {
		case "success": return "var(--fc2-success)";
		case "error": return "var(--fc2-danger)";
		case "warn": return "var(--fc2-warn)";
		default: return "var(--fc2-info)";
	}
};
var createToastContainer = () => h("div", { className: "fc2-toast-container" });
var createToastProgress = (duration, color) => duration > 0 ? h("div", {
	className: "fc2-toast-progress",
	style: {
		"--fc2-toast-color": color,
		"--fc2-toast-duration": `${duration}ms`
	}
}) : null;
var createToastElement = (message, type, duration, showClose, options, onDismiss) => {
	const toast = h("div", {
		className: `fc2-toast-item toast-${type}`,
		onclick: options.onClick
	});
	const dismissToast = (event) => {
		event.stopPropagation();
		onDismiss(toast);
	};
	const actionButton = options.action ? h("button", {
		className: "fc2-toast-action",
		onclick: (event) => {
			event.stopPropagation();
			options.action?.onClick();
			onDismiss(toast);
		}
	}, options.action.label) : null;
	const closeButton = showClose ? h("button", {
		className: "fc2-toast-close",
		onclick: dismissToast
	}, UIUtils.icon(IconXmark)) : null;
	const color = getToastColor(type);
	const icon = h("div", { className: "fc2-toast-icon" }, UIUtils.icon(getToastIconSvg(type)));
	const content = h("span", { className: "fc2-toast-content" }, message);
	const progress = createToastProgress(duration, color);
	toast.append(icon, content);
	if (actionButton) toast.appendChild(actionButton);
	if (closeButton) toast.appendChild(closeButton);
	if (progress) toast.appendChild(progress);
	return toast;
};
var ToastManager = class {
	container = null;
	toasts = /* @__PURE__ */ new Set();
	MAX_TOASTS = MAX_TOASTS;
	init() {
		if (this.container && !this.container.isConnected) {
			this.container = null;
			this.toasts.clear();
		}
		if (this.container) return;
		this.container = createToastContainer();
		UIHost.add(this.container);
	}
	show(message, type = "info", options = {}) {
		this.init();
		const { duration, showClose } = resolveToastDisplayOptions(options);
		const toast = createToastElement(message, type, duration, showClose, options, (el) => this.remove(el));
		log$47.trace(`Showing [${type}]: ${message}`);
		this.container?.appendChild(toast);
		this.toasts.add(toast);
		this.evictOverflowToasts();
		if (duration > 0) setTimeout(() => this.remove(toast), duration);
		if (options.system) BrowserService.notify(message, void 0, void 0, options.onClick);
		return toast;
	}
	remove(toast) {
		if (!this.toasts.has(toast)) return;
		this.toasts.delete(toast);
		toast.classList.add("hiding");
		const onEnd = () => toast.remove();
		const fallbackTimer = setTimeout(onEnd, TIMING.UI_TRANSITION_SLOW);
		toast.addEventListener("animationend", (event) => {
			if (event.animationName === "fc2-toast-out") {
				clearTimeout(fallbackTimer);
				onEnd();
			}
		}, { once: true });
	}
	evictOverflowToasts() {
		while (this.toasts.size > MAX_TOASTS) {
			const oldest = this.toasts.values().next().value;
			if (oldest) this.remove(oldest);
		}
	}
};
var Toast = exports("T",new ToastManager());
var toastBridged = false;
/** Wire toast manager to app events (called once from app/modules). */
function initToastBridge() {
	if (toastBridged) return;
	toastBridged = true;
	CoreEvents.on(AppEvents.SHOW_TOAST, (payload) => {
		Toast.show(payload.message, payload.type);
	});
}
var BatchSelectionService = class {
	selectedIds = /* @__PURE__ */ new Set();
	detachStateListener = null;
	onInit() {
		this.detachStateListener = resolveService(STATE_SERVICE).on("isSelectionMode", (value) => {
			if (!value) this.clearSelection();
		});
	}
	onCleanup() {
		this.detachStateListener?.();
		this.detachStateListener = null;
		this.clearSelection();
	}
	toggleSelection(id) {
		if (this.selectedIds.has(id)) this.selectedIds.delete(id);
		else this.selectedIds.add(id);
		this.emitUpdate();
	}
	setSelection(id, selected) {
		if (selected) this.selectedIds.add(id);
		else this.selectedIds.delete(id);
		this.emitUpdate();
	}
	isSelected(id) {
		return this.selectedIds.has(id);
	}
	getSelectedIds() {
		return Array.from(this.selectedIds);
	}
	getCount() {
		return this.selectedIds.size;
	}
	clearSelection() {
		if (this.selectedIds.size === 0) return;
		this.selectedIds.clear();
		this.emitUpdate();
	}
	selectAll(ids) {
		ids.forEach((id) => this.selectedIds.add(id));
		this.emitUpdate();
	}
	emitUpdate() {
		CoreEvents.emit(AppEvents.SELECTION_CHANGED, {
			ids: this.getSelectedIds(),
			count: this.getCount()
		});
	}
};
var SelectionService = new BatchSelectionService();
var BatchBar = class {
	static element = null;
	static countElement = null;
	static unsubscribers = [];
	static init() {
		this.unsubscribers.forEach((fn) => fn());
		this.unsubscribers = [];
		this.unsubscribers.push(CoreEvents.on(AppEvents.SELECTION_CHANGED, ({ count }) => {
			this.updateVisibility(count);
			this.updateCount(count);
		}));
		this.unsubscribers.push(resolveService(STATE_SERVICE).on("isSelectionMode", (value) => {
			if (!value) this.hide();
			else this.updateVisibility(SelectionService.getCount());
		}));
	}
	static updateVisibility(count) {
		if (resolveService(STATE_SERVICE).proxy.isSelectionMode && count > 0) this.show();
		else this.hide();
	}
	static show() {
		if (!this.element) this.render();
		if (this.element) this.element.classList.add("is-visible");
	}
	static hide() {
		if (this.element) this.element.classList.remove("is-visible");
	}
	static updateCount(count) {
		if (this.countElement) this.countElement.textContent = t("batchSelectionCount", { count });
	}
	static render() {
		this.countElement = h("span", { className: "batch-bar-count" });
		const actions = h("div", { className: "batch-bar-actions" }, h("button", {
			className: "batch-btn primary",
			onclick: () => this.handleMarkViewed(true)
		}, t("batchActionMarkViewed")), h("button", {
			className: "batch-btn",
			onclick: () => this.handleMarkViewed(false)
		}, t("batchActionMarkUnviewed")), h("button", {
			className: "batch-btn",
			onclick: () => this.handleCopyIds()
		}, t("batchActionCopyIds")), h("button", {
			className: "batch-btn accent",
			onclick: () => this.handleExtractMagnets()
		}, t("batchActionExtractMagnets")), h("button", {
			className: "batch-btn danger",
			onclick: () => SelectionService.clearSelection()
		}, t("batchActionCancel")));
		this.element = h("div", {
			id: "fc2-batch-bar",
			className: "fc2-batch-bar"
		}, this.countElement, actions);
		UIHost.add(this.element);
	}
	static async handleMarkViewed(viewed) {
		const ids = SelectionService.getSelectedIds();
		if (ids.length === 0) {
			Toast.show(t("batchNoneSelected"), "info");
			return;
		}
		try {
			if (viewed) await resolveService(HISTORY_SERVICE).addBatch(ids, "watched");
			else await resolveService(HISTORY_SERVICE).removeBatch(ids);
			Toast.show(t("alertSettingsSaved"), "success");
			SelectionService.clearSelection();
			resolveService(STATE_SERVICE).proxy.isSelectionMode = false;
		} catch {
			Toast.show(t("labelError"), "error");
		}
	}
	static handleCopyIds() {
		const ids = SelectionService.getSelectedIds();
		if (ids.length === 0) {
			Toast.show(t("batchNoneSelected"), "info");
			return;
		}
		const text = ids.join("\n");
		navigator.clipboard.writeText(text).then(() => {
			Toast.show(t("tooltipCopied"), "success");
		});
	}
	static async handleExtractMagnets() {
		const ids = SelectionService.getSelectedIds();
		if (ids.length === 0) {
			Toast.show(t("batchNoneSelected"), "info");
			return;
		}
		const total = ids.length;
		Toast.show(t("batchExtractProgress", {
			current: 0,
			total
		}), "info");
		const magnets = [];
		let fail = 0;
		const type = /fc2ppvdb|fd2ppv|fc2cmadb|javfc2|fc2db/i.test(location.hostname) ? "fc2" : "jav";
		for (let i = 0; i < ids.length; i++) {
			const id = ids[i];
			try {
				const url = await resolveService(MAGNET_SERVICE).fetchMagnet(id, type);
				if (url && url.startsWith("magnet:?")) magnets.push(url);
				else fail += 1;
			} catch {
				fail += 1;
			}
			if ((i + 1) % 5 === 0 || i === ids.length - 1) Toast.show(t("batchExtractProgress", {
				current: i + 1,
				total
			}), "info");
		}
		if (magnets.length === 0) {
			Toast.show(t("batchExtractEmpty"), "warn");
			return;
		}
		try {
			await navigator.clipboard.writeText(magnets.join("\n"));
			if (fail > 0) Toast.show(t("batchExtractPartial", {
				ok: magnets.length,
				fail
			}), "warn");
			else Toast.show(t("batchExtractSuccess", { count: magnets.length }), "success");
		} catch {
			Toast.show(t("labelError"), "error");
		}
	}
};
var pendingIntent = null;
var setPendingSettingsNavigationIntent = (intent) => {
	pendingIntent = intent ? { ...intent } : null;
};
var consumePendingSettingsNavigationIntent = exports("c",(targetTab) => {
	if (!pendingIntent || pendingIntent.targetTab !== targetTab) return null;
	const nextIntent = pendingIntent;
	pendingIntent = null;
	return nextIntent;
});
var resolveSettingsFocusGroupsFromIntent = exports("s",(intent) => {
	if (!intent || intent.targetTab !== "settings" || intent.presetKey !== "quick-preferences") return [];
	return [
		"appearance",
		"card-actions",
		"enhancements"
	];
});
var glassEffect = `
    backdrop-filter: blur(var(--fc2-blur));
    -webkit-backdrop-filter: blur(var(--fc2-blur));
    background: var(--fc2-surface-item);
    border: var(--fc2-border-width) solid var(--fc2-border);
`;
var focusRing = `
    &:focus-visible {
        outline: calc(var(--fc2-border-width) * 2) solid var(--fc2-primary);
        outline-offset: var(--fc2-space-xxs);
        box-shadow: var(--fc2-focus-ring);
    }
`;
var rippleEffect = `
    position: relative;
    overflow: hidden;
    &::after {
        content: "";
        position: absolute;
        top: 50%;
        left: 50%;
        width: 100%;
        height: 100%;
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        opacity: 0;
        border-radius: inherit;
        transform: translate(-50%, -50%) scale(1);
        pointer-events: none;
        transition: opacity var(--fc2-motion-slow);
    }
    &:active::after {
        animation: fc2-ripple var(--fc2-motion-slow) var(--fc2-ease-standard);
        opacity: 1;
    }
`;
var minifyCss = (css) => {
	return css.replace(/\/\*(?![!])[\s\S]*?\*\//g, "").replace(/\n/g, "").replace(/\s+/g, " ").replace(/\s*([{}:;,])\s*/g, "$1").trim();
};
var base = (_C) => `
    /* ============================================================
       BASE COMPONENTS & RESET
       ============================================================ */

    /* Global Mono Penetration for industrial feel */
    .fc2-stat-value,
    .fc2-id-badge,
    .fc2-password-wrapper input,
    #debug-log-list {
        font-family:var(--fc2-font-mono)!important;
        letter-spacing:-0.02em;
    }

    *,::before,::after {
        box-sizing:border-box;
    }

    .fc2-icon {
        display:inline-flex;
        align-items:center;
        justify-content:center;
        width:1em;
        height:1em;
        vertical-align:-0.125em;
    }

    .fc2-icon svg {
        width:100%;
        height:100%;
        fill:currentColor;
    }

    .fc2-icon-dropdown-caret {
        display:inline-flex;
        margin-left:var(--fc2-space-xs);
        opacity: var(--fc2-opacity-dim);
        font-size: var(--fc2-text-xs);
        width:1em;
        height:1em;
    }

    .fc2-icon-dropdown-caret svg {
        fill:currentColor;
    }

    .fc2-dim {
        opacity: var(--fc2-opacity-dim);
        color:var(--fc2-text-muted);
    }

    /* ── Utility Classes ───────────────────────────────────────── */

    .fc2-mt-sm { margin-top: var(--fc2-space-sm); }
    .fc2-gap-sm { gap: var(--fc2-space-sm); }
`;
var actionsheet = (_C) => `
    /* ============================================================
       ACTION SHEET (MOBILE)
       ============================================================ */
    .fc2-action-sheet-backdrop {
        position: fixed;
        inset: 0;
        z-index: var(--fc2-z-modal);
        opacity: 0;
        will-change: opacity, visibility;
        visibility: hidden;
        transition: all var(--fc2-motion-base) var(--fc2-ease-standard);
        pointer-events: auto;
    }

    .fc2-action-sheet-backdrop.active {
        opacity: 1;
        visibility: visible;
    }

    .fc2-action-sheet {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        background: var(--fc2-surface-float);
        border-top-left-radius: var(--fc2-radius-xl);
        will-change: transform;
        border-top-right-radius: var(--fc2-radius-xl);
        z-index: calc(var(--fc2-z-modal) + 1);
        transform: translateY(100%);
        transition: transform var(--fc2-motion-slow) var(--fc2-ease-out);
        padding-bottom: calc(var(--fc2-space-lg) + env(safe-area-inset-bottom,0px));
        border: var(--fc2-border-width) solid var(--fc2-border);
        font-family: var(--fc2-font);
        pointer-events: auto;
    }

    .fc2-action-sheet.desktop {
        top: 50%;
        bottom: auto;
        left: 50%;
        right: auto;
        width: 400px;
        max-width: 90vw;
        border-radius: var(--fc2-radius-xl);
        transform: translate(-50%,-50%) scale(0.9);
        box-shadow: var(--fc2-shadow);
        opacity: 0;
        visibility: hidden;
        pointer-events: none;
        transition: all var(--fc2-motion-base) var(--fc2-ease-out);
    }

    .fc2-action-sheet.active {
        transform: translateY(0);
    }

    .fc2-action-sheet.desktop.active {
        transform: translate(-50%,-50%) scale(1);
        opacity: 1;
        visibility: visible;
        pointer-events: auto;
    }

    .fc2-action-sheet-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: var(--fc2-space-md) var(--fc2-space-lg);
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
    }

    .fc2-action-sheet-title {
        font-size: var(--fc2-text-lg) !important;
        font-weight: var(--fc2-font-semibold) !important;
        color: var(--fc2-text);
    }

    .fc2-action-sheet-close-btn {
        background: transparent;
        border: none;
        color: var(--fc2-text-dim);
        font-size: var(--fc2-text-xl) !important;
        cursor: pointer;
        padding: var(--fc2-space-xs);
        line-height: 1;
        transition: color var(--fc2-motion-fast);
    }

    .fc2-action-sheet-close-btn:hover {
        color: var(--fc2-text);
    }

    .fc2-action-sheet-close-btn:focus-visible,
    .fc2-action-sheet-item:focus-visible {
        outline: none;
        box-shadow: 0 0 0 calc(var(--fc2-space-xxs) + var(--fc2-border-width)) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-action-sheet-grid {
        display: grid;
        grid-template-columns: repeat(auto-fill,minmax(calc(var(--fc2-space-2xl) + var(--fc2-space-2xl) + var(--fc2-space-sm) + var(--fc2-space-base)),1fr));
        gap: var(--fc2-space-base);
        padding: var(--fc2-space-lg);
        max-height: 60vh;
        overflow-y: auto;
    }

    .fc2-action-sheet-item {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: var(--fc2-space-sm);
        padding: var(--fc2-space-base);
        background: linear-gradient(180deg, rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost)), var(--fc2-surface-item));
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-btn-radius);
        text-decoration: none !important;
        color: var(--fc2-text-dim) !important;
        transition: all var(--fc2-motion-fast);
        text-align: center;
        box-shadow: inset 0 var(--fc2-border-width) 0 rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
    }

    .fc2-action-sheet-item:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
        color: var(--fc2-text) !important;
        transform: translateY(calc(-1 * var(--fc2-border-width)));
        box-shadow: var(--fc2-shadow-md);
    }

    .fc2-action-sheet-item .fc2-icon {
        font-size: var(--fc2-text-xl);
        margin-bottom: var(--fc2-space-xs);
    }

    .fc2-action-sheet-item span: last-child {
        font-size: var(--fc2-text-xs) !important;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        width: 100%;
    }
`;
var batch = (_C) => `
    .fc2-batch-bar {
        position: fixed;
        bottom: var(--fc2-space-xl);
        left: 50%;
        transform: translateX(-50%) translateY(150%);
        display: flex;
        align-items: center;
        gap: var(--fc2-space-lg);
        padding: var(--fc2-space-sm) var(--fc2-space-lg);
        background: var(--fc2-surface-float);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-full);
        box-shadow: var(--fc2-shadow-lg);
        backdrop-filter: blur(var(--fc2-blur));
        -webkit-backdrop-filter: blur(var(--fc2-blur));
        z-index: var(--fc2-z-modal);
        transition: transform var(--fc2-motion-slow) var(--fc2-ease-spring), opacity var(--fc2-motion-base);
        pointer-events: none;
        opacity: 0;
    }

    .fc2-batch-bar.is-visible {
        transform: translateX(-50%) translateY(0);
        pointer-events: all;
        opacity: 1;
    }

    .batch-bar-count {
        font-size: var(--fc2-text-sm);
        font-weight: var(--fc2-font-bold);
        color: var(--fc2-primary);
        padding-right: var(--fc2-space-md);
        border-right: var(--fc2-border-width) solid var(--fc2-border);
    }

    .batch-bar-actions {
        display: flex;
        gap: var(--fc2-space-sm);
    }

    .batch-btn {
        height: var(--fc2-btn-sm);
        padding: 0 var(--fc2-space-md);
        border-radius: var(--fc2-radius-full);
        font-size: var(--fc2-text-xs);
        font-weight: var(--fc2-font-semibold);
        background: var(--fc2-surface-item);
        border: var(--fc2-border-width) solid var(--fc2-border);
        color: var(--fc2-text);
        cursor: pointer;
        transition: all var(--fc2-motion-fast) var(--fc2-ease-out);
        display: flex;
        align-items: center;
        justify-content: center;
        white-space: nowrap;
    }

    .batch-btn:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        border-color: var(--fc2-primary);
    }

    .batch-btn.primary {
        background: var(--fc2-primary);
        color: var(--fc2-on-primary);
        border-color: var(--fc2-primary);
    }

    .batch-btn.accent {
        background: rgba(var(--fc2-info-rgb), var(--fc2-opacity-muted));
        color: var(--fc2-info);
        border-color: var(--fc2-info);
    }

    .batch-btn.danger {
        color: var(--fc2-danger);
    }
    
    .batch-btn.danger:hover {
        background: rgba(var(--fc2-danger-rgb), var(--fc2-opacity-subtle));
        border-color: var(--fc2-danger);
    }

    @media (max-width: 768px) {
        .fc2-batch-bar {
            width: 90%;
            flex-direction: column;
            border-radius: var(--fc2-radius-lg);
            padding: var(--fc2-space-md);
            bottom: calc(var(--fc2-space-md) + env(safe-area-inset-bottom, 0px));
            gap: var(--fc2-space-md);
        }
        
        .batch-bar-count {
            border-right: none;
            border-bottom: var(--fc2-border-width) solid var(--fc2-border);
            padding-right: 0;
            padding-bottom: var(--fc2-space-xs);
            width: 100%;
            text-align: center;
        }

        .batch-bar-actions {
            flex-wrap: wrap;
            justify-content: center;
        }
    }
`;
/**
* Button Styles
*/
var button = (C) => `
    /* ============================================================
       UNIFIED BUTTON SYSTEM
       ============================================================ */

    .fc2-card-top-right-controls {
        position: absolute;
        top: var(--fc2-space-sm);
        right: var(--fc2-space-sm);
        z-index: var(--fc2-z-dropdown);
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        align-items: stretch;
        ${glassEffect}
        border-radius: var(--fc2-radius-full);
        box-shadow: var(--fc2-shadow-sm);
        overflow: hidden;
        padding: 0;
        gap: 0;
        max-width: calc(100% - (var(--fc2-space-sm) * 2));
    }

    .${C.resourceBtn},
    .fc2-btn-actress,
    .verify-cf-btn {
        position: relative;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 0;
        min-height: var(--fc2-btn-sm);
        background: linear-gradient(180deg, rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)), var(--fc2-surface-item));
        color: var(--fc2-text);
        border: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
        border-radius: var(--fc2-btn-radius);
        font-size: var(--fc2-text-sm);
        font-weight: var(--fc2-font-semibold);
        line-height: 1;
        text-decoration: none;
        backdrop-filter: blur(var(--fc2-blur));
        -webkit-backdrop-filter: blur(var(--fc2-blur));
        cursor: pointer;
        white-space: nowrap;
        box-shadow: inset 0 var(--fc2-border-width) 0 rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
        transition: background-color var(--fc2-motion-base) var(--fc2-ease-out),
                   color var(--fc2-motion-base) var(--fc2-ease-out),
                   border-color var(--fc2-motion-base) var(--fc2-ease-out),
                   box-shadow var(--fc2-motion-base) var(--fc2-ease-out),
                   transform var(--fc2-motion-fast) var(--fc2-ease-out);
    }

    .fc2-card-top-right-controls > * {
        background: transparent !important;
        border: none !important;
        border-radius: 0 !important;
        box-shadow: none !important;
        backdrop-filter: none !important;
        -webkit-backdrop-filter: none !important;
        position: relative;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        color: var(--fc2-text);
        cursor: pointer;
        padding: 0 var(--fc2-space-sm);
        white-space: nowrap;
        margin: 0 !important;
        transition: background-color var(--fc2-motion-base) var(--fc2-ease-out);
    }

    .fc2-card-top-right-controls > *:not(:last-child) {
        border-right: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
    }

    .${C.resourceBtn} *,
    .fc2-fab-btn *,
    .fc2-fab-trigger *,
    .fc2-close-btn *,
    .verify-cf-btn * {
        pointer-events: none !important;
    }

    .${C.resourceBtn}:hover,
    .fc2-btn-actress:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-text);
        border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
    }

    .fc2-card-top-right-controls > *:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
        border-color: transparent;
        transform: translateY(calc(-1 * var(--fc2-border-width)));
        box-shadow: var(--fc2-shadow-md);
    }

    .${C.resourceBtn}:active,
    .fc2-card-top-right-controls > *:active,
    .fc2-btn-actress:active {
        transform: translateY(0) scale(0.98);
    }

    /* Focus-visible for keyboard accessibility */
    .${C.resourceBtn}:focus-visible,
    .fc2-card-top-right-controls > *:focus-visible,
    .fc2-btn-actress:focus-visible,
    .verify-cf-btn:focus-visible {
        ${focusRing.replace("&:focus-visible", "")}
    }

    /* Micro-Interactions */
    .${C.resourceBtn},
    .fc2-fab-btn,
    .fc2-enh-btn,
    .fc2-btn {
        ${rippleEffect}
    }

    .verify-cf-btn {
        margin: var(--fc2-space-xs) auto;
        padding: 0 var(--fc2-space-md);
        color: var(--fc2-warn) !important;
        border-color: rgba(var(--fc2-warn-rgb), var(--fc2-opacity-muted)) !important;
    }

    .verify-cf-btn:hover {
        background: rgba(var(--fc2-warn-rgb), var(--fc2-opacity-muted)) !important;
        color: var(--fc2-text) !important;
        border-color: var(--fc2-warn) !important;
        transform: translateY(calc(-1 * var(--fc2-space-xxs)));
        box-shadow: 0 var(--fc2-space-xs) calc(var(--fc2-space-md) - var(--fc2-border-width)) rgba(var(--fc2-warn-rgb), var(--fc2-opacity-muted)) !important;
    }

    .fc2-card-top-right-controls > * {
        min-width: 0;
        min-height: var(--fc2-btn-sm);
        height: var(--fc2-btn-sm);
        padding: 0 var(--fc2-space-base);
        font-size: var(--fc2-text-xs);
    }

    .${C.resourceBtn} {
        height: var(--fc2-btn-sm);
        padding: 0 var(--fc2-space-base);
        font-size: var(--fc2-text-sm);
    }

    @container (max-width: 250px) {
        .fc2-card-top-right-controls > * {
            height: calc(var(--fc2-btn-sm) - var(--fc2-space-xs));
            padding: 0 calc(var(--fc2-space-sm) - var(--fc2-space-xxs));
            font-size: var(--fc2-text-xs);
        }
.fc2-card-top-right-controls {
            top: calc(var(--fc2-space-sm) - var(--fc2-space-xxs));
            right: calc(var(--fc2-space-sm) - var(--fc2-space-xxs));
            gap: calc(var(--fc2-space-xs) - var(--fc2-border-width));
        }
        .${C.resourceBtn} {
            height: var(--fc2-btn-sm);
            padding: 0 var(--fc2-space-base);
            font-size: var(--fc2-text-xs);
        }
    }

    @container (min-width: 350px) {
        .fc2-card-top-right-controls > * {
            height: calc(var(--fc2-btn-sm) - var(--fc2-space-xxs));
            padding: 0 var(--fc2-space-base);
            font-size: var(--fc2-text-xs);
        }
        .${C.resourceBtn} {
            height: calc(var(--fc2-btn-sm) + var(--fc2-space-xxs));
            padding: 0 var(--fc2-space-md);
            font-size: var(--fc2-text-sm);
        }
    }

    .${C.resourceBtn}.${C.btnMagnet} {
        font-weight: var(--fc2-font-semibold);
        margin-left: auto;
        animation: fc2-magnet-in var(--fc2-motion-slow) var(--fc2-ease-spring);
    }

    .fc2-btn-toggle-view.fc2-is-viewed {
        color: var(--fc2-primary) !important;
    }
    .fc2-btn-toggle-view:not(.fc2-is-viewed) .fc2-icon-viewed { display: none; }
    .fc2-btn-toggle-view:not(.fc2-is-viewed) .fc2-icon-unviewed { display: inline-flex; }
    .fc2-btn-toggle-view.fc2-is-viewed .fc2-icon-viewed { display: inline-flex; }
    .fc2-btn-toggle-view.fc2-is-viewed .fc2-icon-unviewed { display: none; }

    /* 隐藏所有功能按钮的文字以保持图标化风格 */
    .${C.processedCard} .${C.resourceBtn} .${C.buttonText},
    .fc2-enh-toolbar .${C.resourceBtn} .${C.buttonText} {
        display: none !important;
    }

    /* 确保 ID 徽章文字显示 */
    .${C.processedCard} .${C.resourceBtn}.${C.fc2IdBadge} .${C.buttonText},
    .fc2-enh-toolbar .${C.resourceBtn}.${C.fc2IdBadge} .${C.buttonText} {
        display: inline-block !important;
    }

    .fc2-enh-toolbar .${C.resourceBtn}.fc2-btn-actress .${C.buttonText} {
        display: inline-block !important;
    }

    .${C.processedCard} .fc2-card-status-slot .${C.resourceBtn} {
        height: var(--fc2-btn-sm);
        min-height: var(--fc2-btn-sm);
        padding: 0 var(--fc2-space-base);
        font-size: var(--fc2-text-xs);
    }

    .${C.processedCard} .fc2-card-primary-actions .${C.resourceBtn}.${C.btnMagnet} {
        margin-left: 0;
        justify-content: center;
        min-width: 0;
    }

    .fc2-btn-actress {
        margin: var(--fc2-space-xs) auto;
        padding: 0 var(--fc2-space-md);
    }

    .${C.fc2IdBadge} {
        font-family: var(--fc2-font-mono);
        letter-spacing: var(--fc2-letter-tight);
    }

    .${C.fc2IdBadge} .${C.buttonText} {
        font-weight: var(--fc2-font-semibold);
    }

    .${C.fc2IdBadge}.${C.badgeCopied} {
        background: var(--fc2-success) !important;
        color: var(--fc2-on-primary) !important;
        border-color: var(--fc2-success);
        font-weight: var(--fc2-font-bold) !important;
        animation: fc2-copy-success var(--fc2-motion-slow) ease;
    }

    /* Button Industrial Style Override */
    .fc2-btn-industrial {
        position: relative;
        overflow: hidden;
        border: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost)) !important;
        transition: all var(--fc2-motion-fast) var(--fc2-ease-out) !important;
    }

    .fc2-btn-industrial:active {
        transform: scale(0.97) translateY(var(--fc2-space-xxs)) !important;
        background: rgba(var(--fc2-black-rgb), var(--fc2-opacity-muted)) !important;
        box-shadow: inset 0 var(--fc2-space-xxs) var(--fc2-space-xs) rgba(var(--fc2-black-rgb), var(--fc2-opacity-muted)) !important;
    }

    .fc2-btn-industrial.primary {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
        border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted)) !important;
        color: var(--fc2-primary) !important;
    }

    .fc2-enh-btn, .fc2-btn {
        min-height: var(--fc2-btn-md);
        height: var(--fc2-btn-md);
        gap: var(--fc2-space-sm);
        padding: 0 var(--fc2-space-md);
        background: linear-gradient(180deg, rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost)), var(--fc2-surface-item));
        color: var(--fc2-text-dim);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-btn-radius);
        box-shadow: inset 0 var(--fc2-border-width) 0 rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
        font-family: inherit;
        font-size: var(--fc2-text-sm);
        font-weight: var(--fc2-font-semibold);
        cursor: pointer;
        line-height: 1;
        transition: background-color var(--fc2-motion-fast) var(--fc2-ease-standard),
                   color var(--fc2-motion-fast) var(--fc2-ease-standard),
                   border-color var(--fc2-motion-fast) var(--fc2-ease-standard),
                   box-shadow var(--fc2-motion-fast) var(--fc2-ease-standard),
                   transform var(--fc2-motion-fast) var(--fc2-ease-standard);
        display: inline-flex;
        align-items: center;
        justify-content: center;
        white-space: nowrap;
        min-width: 0;
    }

    .fc2-enh-btn .fc2-btn-text, .fc2-btn .fc2-btn-text {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .fc2-enh-btn:hover, .fc2-btn:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-text);
        border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
        transform: translateY(calc(-1 * var(--fc2-border-width)));
        box-shadow: var(--fc2-shadow-md);
    }

    .fc2-enh-btn:focus-visible, .fc2-btn:focus-visible {
        ${focusRing.replace("&:focus-visible", "")}
    }

    .fc2-enh-btn.ghost, .fc2-btn.ghost {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
        border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-text-dim);
    }

    .fc2-enh-btn.ghost:hover, .fc2-btn.ghost:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-text);
        border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-enh-btn.micro, .fc2-btn.micro {
        min-height: var(--fc2-btn-sm);
        height: var(--fc2-btn-sm);
        padding: 0 var(--fc2-space-md);
        border-radius: var(--fc2-btn-radius);
        font-size: var(--fc2-text-xs) !important;
        font-weight: var(--fc2-font-semibold) !important;
    }

    .fc2-enh-btn.icon-only, .fc2-btn.icon-only {
        width: var(--fc2-btn-md);
        padding: 0;
        justify-content: center;
    }

    .fc2-enh-btn.active, .fc2-btn.active {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
        color: var(--fc2-primary);
        border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
    }

    .fc2-enh-btn:disabled, .fc2-btn:disabled {
        opacity: 0.4;
        cursor: not-allowed;
        pointer-events: none;
    }

    .fc2-enh-btn.primary, .fc2-btn.primary {
        background: var(--fc2-primary);
        color: var(--fc2-on-primary);
        border-color: transparent;
        font-weight: var(--fc2-font-bold);
    }

    .fc2-enh-btn.primary:hover, .fc2-btn.primary:hover {
        background: var(--fc2-text);
        transform: translateY(calc(-1 * var(--fc2-border-width)));
        box-shadow: 0 var(--fc2-space-base) var(--fc2-btn-sm) calc(-1 * (var(--fc2-space-md) + var(--fc2-space-xxs))) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-dim));
    }

    .fc2-enh-btn.danger, .fc2-btn.danger {
        color: var(--fc2-danger);
        border-color: rgba(var(--fc2-danger-rgb), var(--fc2-opacity-muted));
    }

    .fc2-enh-btn.danger:hover, .fc2-btn.danger:hover {
        background: rgba(var(--fc2-danger-rgb), 0.08);
        border-color: var(--fc2-danger);
    }

    /* Industrial feedback for buttons in panel */
    .fc2-enh-settings-panel .fc2-enh-btn,
    .fc2-enh-settings-panel .fc2-btn {
        box-shadow: inset 0 var(--fc2-border-width) 0 rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
    }

    .fc2-enh-settings-panel .fc2-enh-btn.primary,
    .fc2-enh-settings-panel .fc2-btn.primary,
    .fc2-enh-settings-panel .fc2-enh-btn.danger,
    .fc2-enh-settings-panel .fc2-btn.danger {
        position: relative;
        overflow: hidden;
        transition: all var(--fc2-motion-fast) var(--fc2-ease-out) !important;
    }

    .fc2-enh-settings-panel .fc2-enh-btn.primary:hover,
    .fc2-enh-settings-panel .fc2-btn.primary:hover {
        transform: translateY(calc(-1 * var(--fc2-border-width)));
        box-shadow: 0 var(--fc2-space-base) var(--fc2-space-lg) calc(-1 * (var(--fc2-space-md) + var(--fc2-space-xxs))) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-dim));
    }

    .fc2-enh-settings-panel .fc2-enh-btn.primary:active,
    .fc2-enh-settings-panel .fc2-btn.primary:active,
    .fc2-enh-settings-panel .fc2-enh-btn.danger:active,
    .fc2-enh-settings-panel .fc2-btn.danger:active {
        transform: scale(0.97) translateY(var(--fc2-border-width));
        box-shadow: inset 0 var(--fc2-space-xxs) var(--fc2-space-xs) rgba(var(--fc2-black-rgb), var(--fc2-opacity-muted));
    }
`;
var card = (C) => `
    /* ============================================================
       CARD SYSTEM
       ============================================================ */

    .${C.cardRebuilt} {
        position: relative;
        display: flex;
        flex-direction: column;
        align-items: stretch;
        padding: 0 !important;
        margin: 0 !important;
        min-width: 0;
        animation: fc2-card-reveal var(--fc2-motion-slow) var(--fc2-ease-out) backwards !important;
        will-change: transform, opacity;
        container-type: inline-size;
        container-name: card;
        transition: none !important;
    }

    .${C.cardRebuilt}.fc2-has-active-dropdown {
        z-index: var(--fc2-z-dropdown) !important;
    }

    /* Global Visibility Toggles */
    body.fc2-hide-viewed-btn .btn-toggle-view { display: none !important; }
    body.fc2-hide-id-badge .${C.fc2IdBadge} { display: none !important; }

    body.fc2-searching .${C.cardRebuilt}:not(.fc2-search-match),
    body.fc2-searching [data-enh-grid-container] > *:has(.${C.cardRebuilt}:not(.fc2-search-match)) {
        display: none !important;
    }

    body.fc2-searching .${C.cardRebuilt}.fc2-search-match {
        animation: fc2-fade-in-scale var(--fc2-motion-slow) var(--fc2-ease-out);
    }

    .${C.processedCard} {
        position: relative;
        display: flex;
        flex-direction: column;
        flex: 1 1 auto;
        min-width: 0;
        height: 100%;
        background: var(--fc2-surface);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-lg);
        overflow: visible;
        box-shadow: var(--fc2-shadow-sm);
        transition: transform var(--fc2-motion-slow) var(--fc2-ease-out),
            box-shadow var(--fc2-motion-slow) var(--fc2-ease-out),
            border-color var(--fc2-motion-base) var(--fc2-ease-standard);
    }

    .${C.processedCard}:hover {
        transform: translateY(-2px) scale(1.005);
        border-color: rgba(var(--fc2-primary-rgb), 0.15);
        box-shadow: var(--fc2-shadow-md);
        z-index: var(--fc2-z-dropdown);
    }

    .${C.processedCard}.fc2-has-active-dropdown,
    .${C.cardRebuilt}.fc2-has-active-dropdown {
        z-index: var(--fc2-z-tooltip) !important;
        overflow: visible !important;
    }

    .${C.processedCard}.${C.isViewed} {
        border-color: var(--fc2-accent) !important;
    }

    /* Detail Page Poster Style (Vertical) */
    .${C.processedCard}.fc2-is-detail {
        width: 100% !important;
        max-width: 1180px !important;
        height: auto !important;
        margin-inline: auto;
        overflow: hidden;
        box-shadow: var(--fc2-shadow-md);
    }

    .${C.processedCard}.fc2-is-detail .${C.videoPreviewContainer} {
        aspect-ratio: auto !important;
        height: auto !important;
        background: transparent !important;
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
    }

    .${C.processedCard}.fc2-is-detail .${C.videoPreviewContainer} img {
        position: static !important;
        display: block !important;
        width: 100% !important;
        height: auto !important;
        object-fit: cover !important;
    }

    .${C.processedCard}.fc2-is-detail .${C.infoArea} {
        gap: var(--fc2-space-sm);
        padding: var(--fc2-space-md) var(--fc2-space-md) var(--fc2-space-lg) !important;
        margin-top: 0 !important;
        background: linear-gradient(180deg, rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)), rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost))) !important;
    }

    .${C.processedCard}.fc2-is-detail .${C.resourceLinksContainer} {
        margin-top: 0 !important;
        flex-wrap: wrap;
    }

    .${C.processedCard}.fc2-is-detail .${C.customTitle} {
        height: auto !important;
        min-height: 0 !important;
        -webkit-line-clamp: 3 !important;
        margin-bottom: var(--fc2-space-xs) !important;
        font-size: var(--fc2-text-lg) !important;
        line-height: var(--fc2-line-tight) !important;
    }

    .${C.processedCard}.fc2-is-detail .${C.preservedIconsContainer} {
        margin-bottom: var(--fc2-space-base);
    }

    .${C.processedCard}.fc2-is-detail .${C.cardMeta} {
        gap: var(--fc2-space-sm);
        margin-bottom: var(--fc2-space-base);
        font-size: var(--fc2-text-xs);
    }

    .${C.processedCard}.fc2-is-detail .fc2-card-meta-row,
    .${C.processedCard}.fc2-is-detail .${C.cardActionRow} {
        gap: var(--fc2-space-base);
    }

    .${C.processedCard}.fc2-is-detail .fc2-card-primary-actions,
    .${C.processedCard}.fc2-is-detail .fc2-card-secondary-actions,
    .${C.processedCard}.fc2-is-detail .${C.resourceLinksContainer} {
        min-width: 0;
    }

    /* Minimal Card (Collection Tab) */
    .${C.processedCard}.fc2-is-minimal {
        height: auto !important;
        min-height: 0;
        padding-top: 0 !important;
        aspect-ratio: auto !important;
        background: var(--fc2-surface) !important;
        border-radius: var(--fc2-radius-md) !important;
        overflow: hidden;
    }

    .${C.processedCard}.fc2-is-minimal .${C.videoPreviewContainer} {
        display: block !important;
    }

    .${C.processedCard}.fc2-is-minimal .fc2-card-top-right-controls {
        top: var(--fc2-space-xs) !important;
        right: var(--fc2-space-xs) !important;
    }

    .${C.processedCard}.fc2-is-minimal .fc2-card-top-right-controls > * {
        display: none !important;
    }

    .${C.processedCard}.fc2-is-minimal .${C.fc2IdBadge} {
        display: none !important;
    }

    .${C.processedCard}.fc2-is-minimal .${C.infoArea} {
        padding: var(--fc2-space-xs) var(--fc2-space-sm) var(--fc2-space-sm) !important;
        background: transparent !important;
        border-top: none !important;
    }

    .${C.processedCard}.fc2-is-minimal .${C.customTitle} {
        height: auto !important;
        -webkit-line-clamp: 1 !important;
        margin-bottom: 0 !important;
        padding-right: var(--fc2-space-xl) !important;
        font-size: var(--fc2-text-xs) !important;
    }

    .${C.processedCard}.fc2-is-minimal .fc2-card-left-actions {
        display: none !important;
    }

    .${C.videoPreviewContainer} {
        position: relative;
        width: 100%;
        aspect-ratio: 16 / 9;
        background: var(--fc2-bg);
        border-top-left-radius: var(--fc2-radius-lg);
        border-top-right-radius: var(--fc2-radius-lg);
        overflow: hidden;
    }

    .${C.videoPreviewContainer} video,
    .${C.videoPreviewContainer} img.${C.staticPreview} {
        width: 100%;
        height: 100%;
        object-fit: contain;
        transition: opacity var(--fc2-motion-base) var(--fc2-ease-out) !important;
        opacity: 0;
        position: absolute !important;
        top: 0 !important;
        left: 0 !important;
        transform: translate3d(0,0,0);
    }

    .${C.videoPreviewContainer} .fc2-reveal-content:not(.${C.hidden}) {
        animation: fc2-content-reveal 0.4s var(--fc2-ease-out) forwards !important;
    }

    .${C.videoPreviewContainer}:not(.fc2-preview-active) img.${C.staticPreview}:not(.${C.hidden}) {
        animation: none !important;
        opacity: 1 !important;
        filter: none !important;
        transform: translate3d(0,0,0) !important;
    }

    .${C.videoPreviewContainer}:not(.fc2-preview-active) video {
        opacity: 0 !important;
        pointer-events: none !important;
    }

    .${C.videoPreviewContainer} .${C.hidden} {
        opacity: 0 !important;
        pointer-events: none !important;
    }

    .${C.processedCard}:hover .${C.videoPreviewContainer} video,
    .${C.processedCard}:hover .${C.videoPreviewContainer} img.${C.staticPreview} {
        transform: translate3d(0,0,0) !important;
    }

    .${C.previewElement} {
        position: absolute;
        top: 0;
        left: 0;
        opacity: 1;
        transition: opacity var(--fc2-motion-slow) var(--fc2-ease-standard);
    }

    .${C.previewElement}.${C.hidden} {
        opacity: 0;
        pointer-events: none;
    }

    /* Pill Action Styles for Play Button */
    .${C.resourceLinksContainer} .${C.btnPlayFullscreen},
    .fc2-card-primary-actions .${C.btnPlayFullscreen} {
        color: var(--fc2-success) !important;
    }

    .${C.resourceLinksContainer} .${C.btnPlayFullscreen}:hover,
    .fc2-card-primary-actions .${C.btnPlayFullscreen}:hover {
        background: rgba(var(--fc2-success-rgb), var(--fc2-opacity-subtle)) !important;
    }

    .${C.infoArea} {
        display: flex;
        flex-direction: column;
        justify-content: flex-end;
        flex-grow: 1;
        padding: var(--fc2-space-base);
        background: var(--fc2-surface-item);
        border-top: var(--fc2-border-width) solid var(--fc2-border);
        border-bottom-left-radius: var(--fc2-radius-lg);
        border-bottom-right-radius: var(--fc2-radius-lg);
    }

    .${C.customTitle} {
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        height: var(--fc2-btn-md);
        margin: 0 0 var(--fc2-space-sm);
        color: var(--fc2-text) !important;
        font-size: var(--fc2-text-sm);
        font-weight: var(--fc2-font-semibold) !important;
        line-height: 1.5;
        text-decoration: none !important;
        overflow: hidden;
        transition: color var(--fc2-motion-fast);
    }

    .${C.customTitle}:hover {
        color: var(--fc2-primary) !important;
    }

    .${C.preservedIconsContainer} {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: var(--fc2-space-xs);
        margin: 0 0 var(--fc2-space-sm);
        min-height: 0;
    }

    .${C.preservedIconsContainer} > * {
        flex: 0 0 auto;
        max-width: 100%;
    }

    .fc2-card-meta-row {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--fc2-space-sm);
        margin-bottom: var(--fc2-space-sm);
    }

    .fc2-card-identity-slot,
    .fc2-card-status-slot,
    .fc2-card-secondary-actions {
        display: flex;
        align-items: center;
        gap: var(--fc2-space-xs);
        min-width: 0;
    }

    .fc2-card-identity-slot {
        flex: 1 1 auto;
        min-width: 0;
    }

    .fc2-card-status-slot {
        flex: 0 0 auto;
    }

    .fc2-card-left-actions {
        display: flex;
        align-items: center;
        justify-content: flex-start;
        gap: var(--fc2-space-xs);
        margin-top: auto;
    }

    .${C.resourceLinksContainer} {
        display: flex;
        flex-direction: row;
        align-items: stretch;
        justify-content: flex-end;
        flex-wrap: nowrap;
        gap: 0 !important;
        max-width: 100%;
        min-width: 0;
        margin-left: auto;

        background: var(--fc2-surface-item);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-full);
        box-shadow: var(--fc2-shadow-sm);
        backdrop-filter: blur(var(--fc2-blur));
        -webkit-backdrop-filter: blur(var(--fc2-blur));
        overflow: hidden;
        padding: 0;
        height: var(--fc2-btn-sm); /* Default height */
    }

    .${C.resourceLinksContainer} > * {
        background: transparent !important;
        border: none !important;
        border-radius: 0 !important;
        box-shadow: none !important;
        backdrop-filter: none !important;
        -webkit-backdrop-filter: none !important;
        position: relative;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        color: var(--fc2-text) !important;
        cursor: pointer;
        padding: 0 var(--fc2-space-sm) !important;
        white-space: nowrap;
        margin: 0 !important;
        height: 100% !important;
        transition: background-color var(--fc2-motion-base) var(--fc2-ease-out);
        flex: 0 0 auto; /* Prevent buttons from stretching horizontally */
    }

    .${C.resourceLinksContainer} > *:not(:last-child) {
        border-right: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
    }

    .${C.resourceLinksContainer} > *:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
    }

    .${C.resourceLinksContainer} .${C.btnMagnet} {
        color: var(--fc2-primary) !important;
        animation: none !important;
        background: transparent !important;
    }

    .${C.resourceLinksContainer} .${C.btnMagnet}:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
    }
    
    /* Visibility Rules are managed globally in base.ts */
    .${C.cardMeta} {
        display: flex;
        flex-wrap: wrap;
        gap: var(--fc2-space-xs);
        margin: 0 0 var(--fc2-space-sm);
        color: var(--fc2-text-dim);
        font-size: var(--fc2-text-xs);
        line-height: 1.4;
    }

    .${C.cardMetaItem} {
        max-width: 100%;
        padding: var(--fc2-space-xxs) var(--fc2-space-sm);
        border: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        border-radius: var(--fc2-radius-full);
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .${C.cardActionRow} {
        display: flex;
        align-items: center;
        justify-content: flex-end;
        gap: var(--fc2-space-sm);
        margin-top: auto;
        min-width: 0;
    }

    .fc2-card-primary-actions,
    .fc2-card-left-actions {
        display: flex;
        flex-direction: row;
        align-items: stretch;
        justify-content: flex-start;
        flex-wrap: nowrap;
        gap: 0 !important;
        flex: 0 1 auto; /* Prevent pill wrapper from stretching */
        min-width: 0;
        margin-left: 0;

        background: var(--fc2-surface-item);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-full);
        box-shadow: var(--fc2-shadow-sm);
        backdrop-filter: blur(var(--fc2-blur));
        -webkit-backdrop-filter: blur(var(--fc2-blur));
        overflow: hidden;
        padding: 0;
        height: var(--fc2-btn-sm);
    }

    .fc2-card-primary-actions:empty,
    .fc2-card-left-actions:empty,
    .fc2-resource-links-container:empty {
        display: none !important;
    }

    .fc2-card-primary-actions > *,
    .fc2-card-left-actions > * {
        background: transparent !important;
        border: none !important;
        border-radius: 0 !important;
        box-shadow: none !important;
        backdrop-filter: none !important;
        -webkit-backdrop-filter: none !important;
        position: relative;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        color: var(--fc2-text) !important;
        cursor: pointer;
        padding: 0 var(--fc2-space-sm) !important;
        white-space: nowrap;
        margin: 0 !important;
        height: 100% !important;
        transition: background-color var(--fc2-motion-base) var(--fc2-ease-out);
        flex: 0 0 auto;
    }

    .fc2-card-primary-actions > *:not(:last-child),
    .fc2-card-left-actions > *:not(:last-child) {
        border-right: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
    }

    .fc2-card-primary-actions > *:hover,
    .fc2-card-left-actions > *:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
    }

    .fc2-card-secondary-actions {
        flex: 0 1 auto;
        min-width: 0;
        margin-left: 0;
        display: flex;
        gap: 0;
    }

    .fc2-card-overflow-actions {
        margin-left: 0;
    }

    .fc2-card-action-stash {
        display: none !important;
    }

    .${C.processedCard}.${C.noMagnet} {
        border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
    }

    body.fc2-card-density-cover-only .${C.infoArea} {
        display: none !important;
    }

    body.fc2-card-density-cover-only .${C.cardRebuilt} {
        container-type: unset;
    }

    body.fc2-card-density-compact .${C.infoArea} {
        padding: var(--fc2-space-xs) var(--fc2-space-sm);
        gap: 0;
    }

    body.fc2-card-density-compact .${C.customTitle} {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        font-size: var(--fc2-text-xs);
        line-height: 1.2;
        -webkit-line-clamp: unset;
        height: auto;
        margin-bottom: 0;
    }

    body.fc2-card-density-compact .${C.cardMeta},
    body.fc2-card-density-compact .fc2-card-status-slot,
    body.fc2-card-density-compact .fc2-card-primary-actions,
    body.fc2-card-density-compact .fc2-card-secondary-actions,
    body.fc2-card-density-compact .${C.resourceLinksContainer} {
        display: none !important;
    }

    body.fc2-card-density-compact .${C.processedCard}:hover .fc2-card-primary-actions,
    body.fc2-card-density-compact .${C.processedCard}:hover .fc2-card-secondary-actions {
        display: flex !important;
    }

    body.fc2-card-density-compact .${C.processedCard}:hover .${C.cardMeta} {
        display: flex !important;
    }

    body.fc2-card-density-standard .${C.customTitle} {
        -webkit-line-clamp: 2;
        height: calc(var(--fc2-btn-md) + var(--fc2-space-xxs));
    }

    body.fc2-card-density-immersive .${C.processedCard} {
        border-radius: var(--fc2-radius-xl);
        box-shadow: var(--fc2-shadow-md);
    }

    body.fc2-card-density-immersive .${C.processedCard}:hover {
        transform: translateY(-4px) scale(1.015);
        box-shadow: var(--fc2-shadow);
    }

    body.fc2-card-density-immersive .${C.infoArea} {
        padding: var(--fc2-space-md);
        gap: var(--fc2-space-base);
        background: linear-gradient(180deg, var(--fc2-surface-item), var(--fc2-surface-low));
    }

    body.fc2-card-density-immersive .${C.customTitle} {
        -webkit-line-clamp: 3;
        height: auto;
        min-height: 4.5em;
        font-size: var(--fc2-text-lg);
        letter-spacing: var(--fc2-letter-tight);
        line-height: 1.5;
    }

    body.fc2-card-density-immersive .${C.cardMeta} {
        font-size: var(--fc2-text-xs);
        margin-bottom: var(--fc2-space-base);
    }

    body.fc2-card-density-immersive .${C.resourceBtn} {
        height: var(--fc2-btn-md);
        padding: 0 var(--fc2-space-md);
        font-size: var(--fc2-text-base);
    }

    @container card (max-width: 280px) {
        .${C.infoArea} {
            padding: var(--fc2-space-xs) var(--fc2-space-sm) !important;
        }

        .fc2-card-meta-row,
        .${C.cardActionRow} {
            flex-wrap: wrap;
            gap: var(--fc2-space-xs) !important;
        }

        .${C.customTitle} {
            -webkit-line-clamp: 2;
            height: auto;
            min-height: calc(var(--fc2-btn-sm) + var(--fc2-space-xxs));
            margin-bottom: var(--fc2-space-xs);
            font-size: var(--fc2-text-xs);
        }

        .fc2-card-primary-actions,
        .fc2-card-secondary-actions,
        .${C.resourceLinksContainer} {
            justify-content: flex-start;
            margin-left: 0;
            height: var(--fc2-btn-sm) !important; /* Shrink buttons for narrow cards */
        }

        .fc2-card-primary-actions .${C.btnMagnet} {
            flex: 1 1 auto;
            min-width: 0;
        }
    }

    /* Ultra-compact mode for very narrow cards (e.g. 3+ columns on mobile) */
    @container card (max-width: 160px) {
        .${C.infoArea} {
            padding: var(--fc2-space-xxs) var(--fc2-space-xs) !important;
        }

        .${C.customTitle} {
            -webkit-line-clamp: 2; /* Allow 2 lines even in ultra-compact */
            font-size: 11px !important;
        }

        /* Keep everything visible, but use wrapping and smaller scales */
        .${C.cardMeta},
        .fc2-card-status-slot,
        .fc2-card-primary-actions,
        .fc2-card-secondary-actions,
        .${C.resourceLinksContainer} {
            display: flex !important;
            flex-wrap: wrap !important;
            gap: var(--fc2-space-xxs) !important;
        }

        .fc2-card-primary-actions,
        .${C.resourceLinksContainer} {
            width: 100% !important;
            height: auto !important; /* Allow growing vertically to fit wrapped buttons */
            min-height: var(--fc2-btn-sm);
            border-radius: var(--fc2-radius-sm) !important;
        }

        .fc2-card-primary-actions > *,
        .${C.resourceLinksContainer} > * {
            flex: 1 1 auto !important;
            padding: 0 var(--fc2-space-xs) !important;
            font-size: 10px !important;
        }
    }

    @media (max-width: 768px) {
        .${C.processedCard}.fc2-is-detail .${C.infoArea} {
            padding: var(--fc2-space-base) !important;
        }

        .${C.processedCard}.fc2-is-detail .${C.customTitle} {
            font-size: var(--fc2-text-base) !important;
        }

    .${C.processedCard}.fc2-is-detail .fc2-card-meta-row,
        .${C.processedCard}.fc2-is-detail .${C.cardActionRow} {
            flex-wrap: wrap;
        }

    .${C.processedCard}.fc2-is-detail .fc2-card-primary-actions,
    .${C.processedCard}.fc2-is-detail .fc2-card-secondary-actions,
        .${C.processedCard}.fc2-is-detail .${C.resourceLinksContainer} {
            justify-content: flex-start;
            width: 100%;
            margin-left: 0;
        }
    }

    /* Selection Overlay */
    .fc2-card-selection-overlay {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        pointer-events: none;
        z-index: var(--fc2-z-dropdown);
        transition: background-color var(--fc2-motion-base);
        border-radius: var(--fc2-radius-lg);
    }

    .fc2-selection-mode-active .fc2-card-selection-overlay {
        pointer-events: all;
        cursor: pointer;
    }

    .fc2-selection-mode-active:hover .fc2-card-selection-overlay {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
    }

    .fc2-is-selected .fc2-card-selection-overlay {
        background: rgba(var(--fc2-primary-rgb), 0.15);
        box-shadow: inset 0 0 0 2px var(--fc2-primary);
    }

    .fc2-card-selection-checkbox {
        position: absolute;
        top: var(--fc2-space-sm);
        left: var(--fc2-space-sm);
        width: 24px;
        height: 24px;
        appearance: none;
        background: var(--fc2-surface-float);
        border: 2px solid var(--fc2-border-strong);
        border-radius: var(--fc2-radius-sm);
        cursor: pointer;
        opacity: 0;
        transform: scale(0.8);
        transition: all var(--fc2-motion-base) var(--fc2-ease-spring);
        display: flex;
        align-items: center;
        justify-content: center;
        margin: 0;
        box-shadow: var(--fc2-shadow-sm);
    }

    .fc2-selection-mode-active .fc2-card-selection-checkbox {
        opacity: 1;
        transform: scale(1);
    }

    .fc2-card-selection-checkbox:checked {
        background: var(--fc2-primary);
        border-color: var(--fc2-primary);
    }

    .fc2-card-selection-checkbox:checked::after {
        content: "";
        width: 6px;
        height: 12px;
        border: solid var(--fc2-on-primary);
        border-width: 0 2.5px 2.5px 0;
        transform: rotate(45deg);
        margin-bottom: 2px;
    }

    .fc2-selection-mode-active .${C.infoArea} {
        pointer-events: none;
    }

    /* ============================================================
       ACTRESS AVATAR & PROFILE
       ============================================================ */
    .fc2-actress-unit {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: 0 10px;
        transition: transform var(--fc2-motion-fast) var(--fc2-ease-standard), opacity var(--fc2-motion-fast) var(--fc2-ease-standard);
    }

    .fc2-actress-unit-linkable {
        cursor: pointer;
    }

    .fc2-actress-avatar-original {
        width: 60px;
        height: 60px;
        border-radius: 50%;
        object-fit: cover;
        border: calc(var(--fc2-border-width) * 2) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
        box-shadow: var(--fc2-shadow-md);
        background-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-actress-avatar-hidden {
        display: none;
    }

    .fc2-actress-name-original {
        margin-top: var(--fc2-space-sm);
        font-size: var(--fc2-text-base);
        font-weight: var(--fc2-font-bold);
        color: var(--fc2-text-bright);
        text-align: center;
        text-shadow: var(--fc2-text-shadow);
        max-width: calc(var(--fc2-space-2xl) + var(--fc2-space-2xl) + 10px);
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .fc2-actress-profile-badge-original {
        display: flex;
        flex-wrap: wrap;
        align-items: flex-start;
        justify-content: center;
        gap: var(--fc2-space-sm);
        margin-bottom: var(--fc2-space-md);
    }

    .fc2-health-indicator {
        margin-left: calc(var(--fc2-space-sm) - var(--fc2-space-xxs));
        font-size: calc(var(--fc2-text-xs) - var(--fc2-space-xxs));
        display: flex;
    }

    .fc2-health-indicator--recent {
        color: var(--fc2-success);
        opacity: 1;
    }

    .fc2-health-indicator--stale {
        color: var(--fc2-text-dim);
        opacity: 0.5;
    }
`;
var common = (_C) => `
    /* ============================================================
       GLOBAL ANIMATIONS (OVERWRITES OR SPECIALS)
       ============================================================ */
    @keyframes fc2-dropdown-in {
        from { transform: translateY(calc(-1 * var(--fc2-space-base))); opacity: 0; }
        to { transform: translateY(0); opacity: 1; }
    }

    /* ============================================================
       DROPDOWN & TOOLTIP
       ============================================================ */
    .fc2-enh-dropdown { position: relative; display: inline-flex; }
    .fc2-enh-dropdown-content {
        position: absolute;
        top: calc(100% + var(--fc2-space-sm));
        right: 0;
        z-index: var(--fc2-z-dropdown);
        display: none;
        flex-direction: column;
        gap: var(--fc2-space-xs);
        min-width: calc(var(--fc2-space-2xl) + var(--fc2-space-2xl) + var(--fc2-space-lg) + var(--fc2-space-lg) + var(--fc2-space-xs) + var(--fc2-space-xxs));
        padding: var(--fc2-space-sm);
        background: rgba(var(--fc2-black-rgb), var(--fc2-opacity-dim));
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-md);
        box-shadow: var(--fc2-shadow-lg);
        backdrop-filter: blur(var(--fc2-blur));
        -webkit-backdrop-filter: blur(var(--fc2-blur));
        animation: fc2-dropdown-in var(--fc2-motion-fast) var(--fc2-ease-standard);
    }
    .fc2-enh-dropdown.active .fc2-enh-dropdown-content { display: flex; }

    .fc2-resource-btn .fc2-tooltip {
        display: none;
        position: absolute;
        bottom: 100%;
        left: 50%;
        transform: translateX(-50%) translateY(calc(-1 * var(--fc2-space-sm)));
        padding: var(--fc2-space-xs) var(--fc2-space-sm);
        background: var(--fc2-surface-float);
        color: var(--fc2-text);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-sm);
        font-size: var(--fc2-text-xs);
        white-space: nowrap;
        pointer-events: none;
        opacity: 0;
        transition: opacity var(--fc2-motion-fast);
        z-index: var(--fc2-z-tooltip);
    }
    .fc2-resource-btn:hover .fc2-tooltip { display: block; opacity: 1; }

    /* ============================================================
       SKELETON & LOADING
       ============================================================ */
    .fc2-skeleton {
        position: relative;
        overflow: hidden !important;
        background: var(--fc2-surface-low);
        border-radius: var(--fc2-radius-sm);
    }
    .fc2-skeleton::after {
        content:'';
        position: absolute;
        inset: 0;
        background: linear-gradient(90deg, transparent, rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost)) 20%, rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) 50%, rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost)) 80%, transparent);
        background-size: 200% 100%;
        animation: fc2-shimmer 2.5s infinite linear;
    }

    /* ============================================================
       LOADING STATES
       ============================================================ */
    .fc2-loading-indicator {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: var(--fc2-space-2xl);
        gap: var(--fc2-space-md);
    }

    .fc2-loading-indicator .fc2-spinner {
        width: var(--fc2-space-2xl);
        height: var(--fc2-space-2xl);
        border: calc(var(--fc2-border-width) * 3) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
        border-top-color: var(--fc2-primary);
        border-radius: 50%;
        animation: fc2-spin 0.8s linear infinite;
    }

    .fc2-loading-indicator-text {
        color: var(--fc2-text-dim);
        font-size: var(--fc2-text-base);
    }

    .fc2-progress-bar {
        width: 100%;
        height: var(--fc2-space-xs);
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        border-radius: var(--fc2-radius-sm);
        overflow: hidden;
    }

    .fc2-progress-fill {
        height: 100%;
        background: var(--fc2-primary);
        transition: width var(--fc2-motion-base) var(--fc2-ease-standard);
        width: var(--fc2-progress, 0%);
    }

    .fc2-fade-out {
        opacity: 0;
        transition: opacity var(--fc2-motion-base) var(--fc2-ease-standard);
    }
`;
var fab = (_C) => `
    /* ============================================================
       ELEGANT FAB (UNIFIED)
       ============================================================ */

    .fc2-fab-container {
        position: fixed;
        bottom: var(--fc2-space-lg);
        right: var(--fc2-space-lg);
        z-index: var(--fc2-z-fab);
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: flex-end;
        gap: var(--fc2-space-base);
        pointer-events: none;
        max-height: 90vh;
    }

    .fc2-fab-trigger,
    .fc2-fab-actions {
        pointer-events: auto;
    }

    .fc2-fab-trigger {
        display: flex;
        align-items: center;
        justify-content: center;
        width: calc(var(--fc2-space-lg) + var(--fc2-space-lg));
        height: calc(var(--fc2-space-lg) + var(--fc2-space-lg));
        background: var(--fc2-accent-grad);
        color: var(--fc2-text);
        border: var(--fc2-liquid-border);
        border-radius: var(--fc2-radius-full);
        box-shadow: var(--fc2-shadow),var(--fc2-rim-light);
        font-size: var(--fc2-text-xl);
        cursor: pointer;
        transition: all var(--fc2-motion-base) var(--fc2-ease-out);
        touch-action: none;
        -webkit-tap-highlight-color: transparent;
        will-change: transform;
        backdrop-filter: blur(var(--fc2-blur));
    }

    .fc2-fab-trigger:hover {
        transform: scale(1.12);
        box-shadow: var(--fc2-shadow);
    }

    .fc2-fab-trigger:active {
        transform: scale(0.92);
    }

    .fc2-fab-trigger.active {
        transform: rotate(135deg);
        background: var(--fc2-primary);
        color: var(--fc2-on-primary);
        animation: fc2-float 3s ease-in-out infinite;
    }

    .fc2-fab-trigger.active:hover {
        transform: scale(1.1) rotate(135deg);
        animation-play-state: paused;
    }

    .fc2-fab-actions {
        display: flex;
        flex-direction: column;
        gap: var(--fc2-space-base);
        opacity: 0;
        transform: translateY(var(--fc2-space-lg)) scale(0.8);
        pointer-events: none;
        transition: all var(--fc2-motion-slow) var(--fc2-ease-out);
        max-height: 60vh;
        width: calc(var(--fc2-space-2xl) + var(--fc2-space-lg));
        overflow-y: auto !important;
        overflow-x: hidden;
        padding: var(--fc2-space-sm);
        margin-bottom: var(--fc2-space-sm);
        background: var(--fc2-liquid-bg);
        backdrop-filter: blur(var(--fc2-blur)) saturate(var(--fc2-glass-saturate));
        -webkit-backdrop-filter: blur(var(--fc2-blur)) saturate(var(--fc2-glass-saturate));
        border: var(--fc2-liquid-border);
        box-shadow: var(--fc2-shadow),var(--fc2-rim-light);
        border-radius: var(--fc2-radius-lg);
        scrollbar-width: thin;
        scrollbar-color: var(--fc2-primary) transparent;
        touch-action: pan-y;
        position: relative;
    }

    .fc2-fab-actions::before {
        content: "";
        position: absolute;
        inset: 0;
        background: var(--fc2-glass-noise);
        opacity: 0.05;
        pointer-events: none;
        z-index: 0;
    }

    .fc2-fab-actions::-webkit-scrollbar {
        width: calc(var(--fc2-space-sm) - var(--fc2-space-xxs));
        display: block !important;
    }
    .fc2-fab-actions::-webkit-scrollbar-track {
        background: transparent;
    }
    .fc2-fab-actions::-webkit-scrollbar-thumb {
        background-color: var(--fc2-primary);
        border-radius: var(--fc2-radius-full);
        border: var(--fc2-border-width) solid var(--fc2-border);
    }

    .fc2-fab-actions.visible {
        opacity: 1;
        transform: translateY(0) scale(1);
        pointer-events: auto;
    }

    .fc2-fab-actions.visible .fc2-fab-btn:nth-child(1) { transition-delay: 0.05s; }
    .fc2-fab-actions.visible .fc2-fab-btn:nth-child(2) { transition-delay: 0.1s; }
    .fc2-fab-actions.visible .fc2-fab-btn:nth-child(3) { transition-delay: 0.15s; }
    .fc2-fab-actions.visible .fc2-fab-btn:nth-child(4) { transition-delay: 0.2s; }
    .fc2-fab-actions.visible .fc2-fab-btn:nth-child(5) { transition-delay: 0.25s; }

    .fc2-fab-btn {
        position: relative;
        display: flex;
        align-items: center;
        justify-content: center;
        width: var(--fc2-btn-lg);
        height: var(--fc2-btn-lg);
        background: var(--fc2-surface-item);
        color: var(--fc2-text-dim);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-full);
        font-size: var(--fc2-text-lg);
        cursor: pointer;
        transition: all var(--fc2-motion-base) var(--fc2-ease-out);
        -webkit-tap-highlight-color: transparent;
        z-index: var(--fc2-z-fab);
    }

    .fc2-fab-btn:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-text);
        border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
        transform: scale(1.15);
        box-shadow: var(--fc2-shadow-md);
    }

    .fc2-fab-btn:focus-visible,
    .fc2-fab-trigger:focus-visible {
        outline: none;
        box-shadow: 0 0 0 calc(var(--fc2-space-xxs) + var(--fc2-border-width)) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-fab-btn.active {
        background: var(--fc2-primary);
        color: var(--fc2-on-primary);
        border-color: transparent;
        box-shadow: 0 0 calc(var(--fc2-space-sm) + var(--fc2-space-base)) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-dim));
    }

    .fc2-fab-btn::before {
        content: attr(data-title);
        position: absolute;
        right: calc(var(--fc2-space-2xl) + var(--fc2-space-base));
        top: 50%;
        visibility: hidden;
        padding: var(--fc2-space-xs) var(--fc2-space-sm);
        background: rgba(var(--fc2-black-rgb), var(--fc2-opacity-strong));
        color: var(--fc2-on-primary);
        border-radius: var(--fc2-radius-sm);
        font-size: var(--fc2-text-xs);
        white-space: nowrap;
        opacity: 0;
        backdrop-filter: blur(var(--fc2-blur));
        transform: translateY(-50%) translateX(var(--fc2-space-xs));
        transition: all var(--fc2-motion-fast);
        pointer-events: none;
    }

    .fc2-fab-btn:hover::before {
        visibility: visible;
        opacity: 1;
        transform: translateY(-50%) translateX(0);
    }

    .fc2-sync-dot {
        position: absolute;
        top: calc(-1 * var(--fc2-space-xxs));
        right: calc(-1 * var(--fc2-space-xxs));
        width: var(--fc2-btn-sm);
        height: var(--fc2-btn-sm);
        background: var(--fc2-text-muted);
        border: var(--fc2-border-width) solid var(--fc2-bg);
        border-radius: var(--fc2-radius-full);
        transition: all var(--fc2-motion-base);
    }

    .fc2-sync-dot.syncing {
        background: var(--fc2-info);
        animation: fc2-pulse-sync 1.5s infinite;
    }

    .fc2-sync-dot.success { background: var(--fc2-success); }
    .fc2-sync-dot.error { background: var(--fc2-danger); }
    .fc2-sync-dot.conflict { background: var(--fc2-warn); }
`;
var form = (_C) => `
    /* ============================================================
       FORM ELEMENTS (GLASSMORPHISM)
       ============================================================ */

    .fc2-enh-form-row {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        margin-bottom: 0;
        padding: var(--fc2-space-sm) var(--fc2-space-md);
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
        transition: background-color var(--fc2-motion-fast);
        min-height: 0;
    }

    .fc2-enh-form-row:last-child {
        border-bottom: none;
    }

    .fc2-enh-form-row:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
    }

    .fc2-enh-label {
        display: block;
        margin-bottom: var(--fc2-space-sm);
        color: var(--fc2-text);
        font-size: var(--fc2-text-xs);
        font-weight: var(--fc2-font-semibold);
        letter-spacing: 0.02em;
        opacity: var(--fc2-opacity-dim);
    }

    .fc2-enh-select, .fc2-enh-input, .fc2-enh-textarea {
        width: 100%;
        padding: var(--fc2-space-sm) var(--fc2-space-base);
        background: rgba(var(--fc2-black-rgb), var(--fc2-opacity-muted)) !important;
        color: var(--fc2-text) !important;
        border: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        border-radius: var(--fc2-radius-sm);
        font-family: inherit;
        font-size: var(--fc2-text-sm);
        font-weight: var(--fc2-font-medium);
        outline: none;
        transition: all var(--fc2-motion-fast) var(--fc2-ease-out);
        appearance: none;
        -webkit-appearance: none;
    }

    .fc2-enh-select option {
        background: var(--fc2-bg);
        color: var(--fc2-text);
        padding: var(--fc2-space-sm);
    }

    .fc2-enh-select option: checked,
    .fc2-enh-select option: hover {
        background: var(--fc2-primary) !important;
        color: var(--fc2-on-primary) !important;
    }

    .fc2-enh-select:focus,
    .fc2-enh-input:focus,
    .fc2-enh-textarea:focus {
        border-color: var(--fc2-primary);
        background: var(--fc2-surface) !important;
        box-shadow: 0 0 0 calc(var(--fc2-space-xxs) + var(--fc2-border-width)) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-enh-checkbox-label {
        display: flex;
        align-items: center;
        gap: var(--fc2-space-sm);
        cursor: pointer;
        user-select: none;
        padding: var(--fc2-space-xs) 0;
    }

    .fc2-enh-form-row input[type="checkbox"] {
        position: relative;
        appearance: none;
        -webkit-appearance: none;
        width: 1.25rem;
        height: 1.25rem;
        background: var(--fc2-surface-lowest);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-sm);
        cursor: pointer;
        transition: all var(--fc2-motion-fast) var(--fc2-ease-out);
        display: flex;
        align-items: center;
        justify-content: center;
    }

    .fc2-enh-form-row input[type="checkbox"]:focus-visible {
        outline: none;
        box-shadow: 0 0 0 calc(var(--fc2-space-xxs) + var(--fc2-border-width)) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-enh-form-row input[type="checkbox"]:checked {
        background: var(--fc2-primary);
        border-color: var(--fc2-primary);
    }

    .fc2-enh-form-row input[type="checkbox"]::after {
        content: "";
        width: var(--fc2-space-sm);
        height: var(--fc2-space-sm);
        background-color: var(--fc2-liquid-bg);
        clip-path: polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0%,43% 62%);
        transform: scale(0);
        transition: transform var(--fc2-motion-fast) var(--fc2-ease-spring);
    }

    .fc2-enh-form-row input[type="checkbox"]:checked::after {
        transform: scale(1);
    }

    .fc2-enh-checkbox-text {
        font-size: var(--fc2-text-sm);
        color: var(--fc2-text);
        font-weight: var(--fc2-font-semibold);
        opacity: var(--fc2-opacity-dim);
    }

    /* Range Input */
    .fc2-enh-range {
        -webkit-appearance: none;
        width: 100%;
        height: var(--fc2-space-xs);
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        border-radius: var(--fc2-radius-sm);
        outline: none;
    }

    .fc2-enh-range::-webkit-slider-thumb {
        -webkit-appearance: none;
        width: var(--fc2-text-base);
        height: var(--fc2-text-base);
        border-radius: var(--fc2-radius-full);
        background: var(--fc2-primary);
        cursor: pointer;
        box-shadow: 0 0 0 var(--fc2-space-xxs) var(--fc2-bg);
    }

    .fc2-enh-form-row.checkbox {
        flex-direction: row;
        justify-content: flex-start;
        align-items: center;
        gap: var(--fc2-space-base);
        padding: var(--fc2-space-sm) var(--fc2-space-md);
        margin: 0;
        background: transparent;
        border: none;
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: 0;
        transition: all var(--fc2-motion-fast) var(--fc2-ease-out);
        cursor: pointer;
        width: 100%;
        box-sizing: border-box;
    }

    .fc2-enh-form-row.checkbox:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-enh-form-row.checkbox:has(input[type="checkbox"]:checked) {
        background: rgba(var(--fc2-primary-rgb), 0.02);
    }

    .fc2-enh-form-row.checkbox:has(input[type="checkbox"]:checked) .fc2-enh-checkbox-text {
        opacity: 1;
        font-weight: var(--fc2-font-semibold);
        color: var(--fc2-text) !important;
    }

    .fc2-input-group {
        display: flex;
        gap: var(--fc2-space-sm);
        align-items: center;
        width: 100%;
    }

    .fc2-input-group .fc2-enh-input {
        flex: 1;
        min-width: 0;
    }

    .fc2-input-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: var(--fc2-btn-md);
        height: var(--fc2-btn-md);
        flex-shrink: 0;
        padding: 0;
        background: var(--fc2-surface-item);
        color: var(--fc2-text-dim);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-sm);
        cursor: pointer;
        transition: all var(--fc2-motion-fast) var(--fc2-ease-standard);
    }

    .fc2-input-toggle:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-text);
    }

    .fc2-input-toggle:focus-visible {
        outline: none;
        box-shadow: 0 0 0 calc(var(--fc2-space-xxs) + var(--fc2-border-width)) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-input-toggle svg {
        width: var(--fc2-text-base);
        height: var(--fc2-text-base);
        fill: currentColor;
    }

    /* Hide Native Edge/IE reveal icons as we use our own */
    input[type="password"]::-ms-reveal,
    input[type="password"]::-ms-clear {
        display: none !important;
    }
`;
var gallery = (_C) => `
    /* ============================================================
       GALLERY & VIEWER
       ============================================================ */
    .fc2-enh-viewer-backdrop {
        position:fixed;
        inset:0;
        z-index:var(--fc2-z-modal);
        display:flex;
        flex-direction:column;
        background:var(--fc2-surface-float);
        backdrop-filter:blur(calc(var(--fc2-blur) * 2)) saturate(var(--fc2-glass-saturate));
        -webkit-backdrop-filter:blur(calc(var(--fc2-blur) * 2)) saturate(var(--fc2-glass-saturate));
        pointer-events: auto;
    }

    .fc2-enh-viewer-stage {
        position:relative;
        display:flex;
        flex:1;
        align-items:center;
        justify-content:center;
        overflow:hidden;
        padding:var(--fc2-space-md);
        width: 100%;
    }

    .fc2-enh-viewer-stage.zoomed {
        overflow:auto;
        align-items:flex-start;
        justify-content:flex-start;
    }

    .fc2-enh-viewer-stage img, .fc2-enh-viewer-stage video {
        max-width:100%;
        max-height:100%;
        object-fit:contain;
        border-radius:var(--fc2-radius-lg);
        box-shadow:var(--fc2-shadow);
        transition:transform var(--fc2-motion-base) var(--fc2-ease-out);
        will-change:transform;
    }

    .fc2-enh-viewer-stage-image {
        cursor:zoom-in;
        width:100%;
        height:100%;
        object-fit:contain;
    }

    .fc2-enh-viewer-stage-image.zoomed {
        cursor:zoom-out;
        max-width:none;
        max-height:none;
        width:auto;
        height:auto;
        margin:auto;
        transform:scale(1);
    }

    .slide-next img, .slide-next video { animation: fc2-slide-right-in var(--fc2-motion-base) var(--fc2-ease-out); }
    .slide-prev img, .slide-prev video { animation: fc2-slide-left-in var(--fc2-motion-base) var(--fc2-ease-out); }
    .slide-init img, .slide-init video { animation: fc2-fade-in var(--fc2-motion-base) var(--fc2-ease-out); }

    .fc2-enh-viewer-nav {
        position:absolute;
        top:50%;
        transform:translateY(-50%);
        z-index:var(--fc2-z-toast);
        width:calc(var(--fc2-space-lg) + var(--fc2-space-lg));
        height:calc(var(--fc2-space-lg) + var(--fc2-space-lg));
        display:flex;
        align-items:center;
        justify-content:center;
        background:rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        border-radius: var(--fc2-radius-full);
        cursor:pointer;
        transition:all var(--fc2-motion-fast);
        color: var(--fc2-text);
    }

    .fc2-enh-viewer-nav:hover {
        background:rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
        transform:translateY(-50%) scale(1.1);
    }

    .fc2-enh-viewer-nav.prev { left:calc(var(--fc2-space-sm) + var(--fc2-space-base)); }
    .fc2-enh-viewer-nav.next { right:calc(var(--fc2-space-sm) + var(--fc2-space-base)); }

    .fc2-enh-viewer-nav-icon {
        transform:scale(1.5);
    }

    .fc2-enh-viewer-close {
        position:absolute;
        top:calc(var(--fc2-space-sm) + var(--fc2-space-base));
        right:calc(var(--fc2-space-sm) + var(--fc2-space-base));
        z-index:var(--fc2-z-toast);
        width:var(--fc2-space-2xl);
        height:var(--fc2-space-2xl);
        display:flex;
        align-items:center;
        justify-content:center;
        background:rgba(var(--fc2-black-rgb),var(--fc2-opacity-muted));
        border-radius: var(--fc2-radius-full);
        cursor:pointer;
        color:var(--fc2-text);
        transition:background var(--fc2-motion-fast);
    }

    .fc2-enh-viewer-close:hover { background:rgba(var(--fc2-black-rgb),var(--fc2-opacity-dim)); }

    .fc2-enh-viewer-actions {
        position:absolute;
        bottom:calc(var(--fc2-space-2xl) + var(--fc2-space-2xl) + var(--fc2-space-sm) + var(--fc2-space-base));
        right:calc(var(--fc2-space-sm) + var(--fc2-space-base));
        z-index:var(--fc2-z-toast);
        display:flex;
        flex-direction:column;
        gap:var(--fc2-space-base);
    }

    .fc2-enh-viewer-action {
        width:var(--fc2-space-2xl);
        height:var(--fc2-space-2xl);
        border-radius: var(--fc2-radius-full);
        background:rgba(var(--fc2-black-rgb), var(--fc2-opacity-muted));
        border: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        color:var(--fc2-text);
        display:flex;
        align-items:center;
        justify-content:center;
        cursor:pointer;
        transition:all var(--fc2-motion-fast);
    }

    .fc2-enh-viewer-action:hover {
        background:var(--fc2-primary);
        color:var(--fc2-on-primary);
    }

    .fc2-enh-viewer-action.fc2-hidden {
        display:none;
    }

    .fc2-enh-viewer-counter {
        position:absolute;
        top:calc(var(--fc2-space-sm) + var(--fc2-space-base));
        left:calc(var(--fc2-space-sm) + var(--fc2-space-base));
        z-index:var(--fc2-z-toast);
        background:rgba(var(--fc2-black-rgb),var(--fc2-opacity-dim));
        padding: var(--fc2-space-xs) var(--fc2-space-base);
        border-radius:var(--fc2-radius-xl);
        color:var(--fc2-text);
        font-size: var(--fc2-text-base);
    }

    .fc2-enh-viewer-counter-current {
        color:var(--fc2-text-bright);
        font-weight:var(--fc2-font-bold);
    }

    .fc2-enh-viewer-counter-separator {
        opacity:var(--fc2-opacity-dim);
        margin:0 var(--fc2-space-xs);
    }

    .fc2-enh-viewer-thumbs {
        height:calc(var(--fc2-space-2xl) + var(--fc2-space-2xl));
        width:100%;
        display:flex;
        gap:var(--fc2-space-sm);
        padding:var(--fc2-space-sm) calc(var(--fc2-space-sm) + var(--fc2-space-base));
        overflow-x:auto;
        background:rgba(var(--fc2-black-rgb),var(--fc2-opacity-muted));
        z-index:var(--fc2-z-toast);
    }

    .fc2-enh-viewer-single .pb-play,
    .fc2-enh-viewer-single .fc2-enh-viewer-counter,
    .fc2-enh-viewer-single .fc2-enh-viewer-thumbs {
        display:none;
    }

    .fc2-enh-thumb-item {
        height:100%;
        aspect-ratio:16/9;
        border-radius:var(--fc2-radius-sm);
        overflow:hidden;
        cursor:pointer;
        opacity:var(--fc2-opacity-dim);
        transition:opacity var(--fc2-motion-fast);
        border:calc(var(--fc2-border-width) * 2) solid transparent;
        flex-shrink:0;
    }

    .fc2-enh-thumb-item.active {
        opacity:1;
        border-color:var(--fc2-primary);
    }

    .fc2-enh-thumb-item img, .fc2-enh-thumb-item video {
        width:100%;
        height:100%;
        object-fit:cover;
    }

    /* ============================================================
       PREVIEW STATES (loading, error, active)
       ============================================================ */
    .fc2-enh-viewer-stage-media {
        width:100%;
        height:100%;
        object-fit:contain;
        border-radius:var(--fc2-radius-md);
    }

    .preview-loading {
        position:absolute;
        inset:0;
        display:flex;
        flex-direction:column;
        align-items:center;
        justify-content:center;
        background:linear-gradient(180deg, var(--fc2-surface-low), var(--fc2-surface-float));
        z-index:var(--fc2-z-tooltip);
    }

    .preview-spinner {
        width:var(--fc2-space-2xl);
        height:var(--fc2-space-2xl);
        border:calc(var(--fc2-border-width) * 3) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
        border-top-color:var(--fc2-text-bright);
        border-radius:50%;
        animation:fc2-spin 0.8s linear infinite;
    }

    .preview-progress {
        margin-top:10px;
        color:var(--fc2-text-bright);
        font-size:var(--fc2-text-xs);
        text-align:center;
    }

    .preview-error {
        position:absolute;
        top:50%;
        left:50%;
        transform:translate(-50%, -50%);
        color:var(--fc2-danger);
        font-size:var(--fc2-text-xs);
        text-align:center;
        z-index:var(--fc2-z-tooltip);
    }

    .preview-error-icon {
        font-size:var(--fc2-space-lg);
        margin-bottom:var(--fc2-space-xs);
        display:inline-block;
    }
`;
var modal = (_C) => `
    /* ============================================================
       MODAL & SETTINGS PANEL
       ============================================================ */

    .fc2-enh-modal-backdrop {
        position: absolute;
        inset: 0;
        z-index: var(--fc2-z-modal);
        background: rgba(var(--fc2-black-rgb), var(--fc2-opacity-muted));
        transition: background-color var(--fc2-motion-base) var(--fc2-ease-standard), backdrop-filter var(--fc2-motion-base) var(--fc2-ease-standard);
        backdrop-filter: blur(var(--fc2-blur));
        -webkit-backdrop-filter: blur(var(--fc2-blur));
        pointer-events: auto;
    }

    .fc2-enh-modal-panel {
        position: fixed;
        top: 50%;
        left: 50%;
        z-index: var(--fc2-z-modal);
        background-color: var(--fc2-liquid-bg);
        color: var(--fc2-text);
        border: var(--fc2-border-width) solid transparent;
        background-clip: padding-box,border-box;
        background-origin: padding-box,border-box;
        background-image: linear-gradient(to bottom,transparent,transparent), var(--fc2-liquid-iridescent);
        border-radius: var(--fc2-radius-lg);
        box-shadow: var(--fc2-rim-light), var(--fc2-shadow);
        overflow: hidden;
        animation: fc2-pop-in var(--fc2-motion-slow) var(--fc2-ease-out);
        transform: translate(-50%,-50%);
        will-change: transform;
        pointer-events: auto;
    }

    .fc2-enh-settings-panel {
        width: min(1180px, calc(100vw - var(--fc2-space-xl)));
        height: min(900px, calc(100vh - var(--fc2-space-xl)));
        height: min(900px, calc(100dvh - var(--fc2-space-xl)));
        display: flex;
        flex-direction: column;
        overflow: hidden;
        position: relative;
        z-index: var(--fc2-z-modal) !important;
        background: var(--fc2-liquid-bg);
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        box-shadow: inset 0 var(--fc2-border-width) 0 rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)), var(--fc2-shadow);
        pointer-events: auto;
        border-radius: var(--fc2-radius-lg);
        border: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-enh-settings-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--fc2-space-md);
        padding: var(--fc2-space-md) var(--fc2-space-lg);
        background: linear-gradient(180deg, rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)), rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost)));
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
        flex-shrink: 0;
        z-index: var(--fc2-z-modal);
    }

    .fc2-enh-settings-heading {
        display: flex;
        align-items: center;
        min-width: 0;
    }

    .fc2-enh-settings-header h2 {
        margin: 0;
        font-size: var(--fc2-text-lg);
        font-weight: var(--fc2-font-bold);
        line-height: var(--fc2-line-tight);
        letter-spacing: 0.01em;
    }

    .fc2-header-actions {
        display: flex;
        align-items: center;
        gap: var(--fc2-space-sm);
        flex-shrink: 0;
    }

    .fc2-enh-settings-body {
        display: flex;
        flex: 1;
        min-height: 0;
        overflow: hidden;
    }

    .fc2-enh-settings-content-shell {
        display: flex;
        flex: 1;
        min-width: 0;
        min-height: 0;
        flex-direction: column;
        overflow: hidden;
    }

    .fc2-enh-settings-content {
        position: relative;
        flex: 1;
        min-height: 0;
        padding: 0;
        overflow: hidden;
        background: linear-gradient(180deg, rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)), transparent 1180px);
        display: flex;
        flex-direction: column;
    }

    .fc2-close-btn {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: var(--fc2-space-2xl);
        height: var(--fc2-space-2xl);
        padding: 0;
        background: transparent;
        color: var(--fc2-text-dim);
        border: var(--fc2-border-width) solid transparent;
        border-radius: var(--fc2-radius-sm);
        cursor: pointer;
        transition: background-color var(--fc2-motion-fast) var(--fc2-ease-standard), color var(--fc2-motion-fast) var(--fc2-ease-standard), border-color var(--fc2-motion-fast) var(--fc2-ease-standard), box-shadow var(--fc2-motion-fast) var(--fc2-ease-standard);
    }

    .fc2-close-btn:hover {
        background: var(--fc2-surface-item);
        color: var(--fc2-text);
        border-color: var(--fc2-border);
    }

    .fc2-close-btn:focus-visible {
        outline: none;
        box-shadow: var(--fc2-focus-ring);
    }

    .fc2-loading-overlay {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: var(--fc2-space-base);
        height: 100%;
        min-height: 200px;
        color: var(--fc2-text-dim);
    }

    .fc2-loading-spinner {
        width: var(--fc2-space-xl);
        height: var(--fc2-space-xl);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-top-color: var(--fc2-primary);
        border-radius: var(--fc2-radius-full);
        animation: fc2-spin 0.8s linear infinite;
    }

    .fc2-loading-text {
        font-size: var(--fc2-text-sm) !important;
        opacity: 0.6;
    }

    .fc2-error-state {
        display: flex;
        align-items: center;
        justify-content: center;
        height: 100%;
        min-height: 200px;
        color: var(--fc2-danger);
        font-size: var(--fc2-text-base) !important;
    }

    @media(max-width:768px) {
        .fc2-enh-settings-body { flex-direction: column; }
        .fc2-enh-settings-content-shell { order: 1; }
        .fc2-enh-settings-panel {
            width: 100vw;
            height: 100vh;
            height: 100dvh;
            border-radius: 0;
            border: none;
        }
        .fc2-enh-settings-content {
            overflow-y: auto;
            -webkit-overflow-scrolling: touch;
            overscroll-behavior: contain;
        }
        .fc2-enh-settings-header { padding: 0.85rem 1rem; }
    }
`;
var settings = (_C) => `
    /* ============================================================
       SETTINGS GROUPS & PORTALS
       ============================================================ */

    .fc2-panel-card {
        display: flex;
        flex-direction: column;
        gap: 0;
        padding: 0;
        background: var(--fc2-surface-item);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-xl);
        box-shadow: var(--fc2-shadow-sm);
        backdrop-filter: blur(calc(var(--fc2-blur) * 1.5));
        -webkit-backdrop-filter: blur(calc(var(--fc2-blur) * 1.5));
        transition: all var(--fc2-motion-fast) var(--fc2-ease-standard);
        height: 100%;
        position: relative;
        overflow: hidden;
        will-change: background-color, border-color, transform;
    }

    .fc2-panel-card:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-panel-card-heading {
        display: flex;
        align-items: center;
        gap: var(--fc2-space-sm);
        margin: 0;
        padding: var(--fc2-space-base) var(--fc2-space-md);
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
        transition: background-color var(--fc2-motion-fast);
    }

    .fc2-panel-card-heading .fc2-icon {
        font-size: var(--fc2-text-lg);
        opacity: var(--fc2-opacity-dim);
        color: var(--fc2-text);
    }

    .fc2-card-title {
        font-size: var(--fc2-text-sm);
        font-weight: var(--fc2-font-semibold);
        color: var(--fc2-text);
        opacity: var(--fc2-opacity-full);
    }

    .fc2-card-subtitle {
        font-size: var(--fc2-text-xs) !important;
        color: var(--fc2-text-muted);
        margin-left: auto;
        opacity: var(--fc2-opacity-dim);
        white-space: nowrap;
    }

    .fc2-panel-card-body {
        display: flex;
        flex-direction: column;
        padding: 0;
        margin: 0;
        background: transparent;
    }

    .fc2-portal-grid {
        display: grid;
        grid-template-columns: repeat(auto-fill,minmax(180px,1fr));
        gap: 0;
        width: 100%;
        box-sizing: border-box;
    }

    .fc2-portal-item {
        display: flex;
        align-items: center;
        gap: var(--fc2-space-base);
        padding: var(--fc2-space-sm) var(--fc2-space-md);
        border: none;
        border-bottom: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        border-radius: 0;
        cursor: pointer;
        transition: all var(--fc2-motion-base) var(--fc2-ease-out);
        background: transparent;
    }

    .fc2-portal-item:last-child {
        border-bottom: none;
    }

    .fc2-portal-item span {
        color: var(--fc2-text) !important;
        font-weight: var(--fc2-font-medium) !important;
        font-size: var(--fc2-text-sm) !important;
        opacity: var(--fc2-opacity-strong);
    }

    .fc2-portal-item:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-portal-item.active {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
        box-shadow: inset 0 0 0 var(--fc2-border-width) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-filter-chip {
        padding: var(--fc2-space-xs) var(--fc2-space-base);
        background: var(--fc2-surface-item);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-lg);
        font-size: var(--fc2-text-xs);
        cursor: pointer;
        transition: all var(--fc2-motion-fast) var(--fc2-ease-standard);
        color: var(--fc2-text-dim);
    }

    .fc2-filter-chip.active {
        background: var(--fc2-primary);
        border-color: var(--fc2-primary);
        color: var(--fc2-on-primary);
        box-shadow: 0 0 var(--fc2-space-base) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
    }

    .fc2-filter-chip:focus-visible {
        outline: none;
        box-shadow: 0 0 0 calc(var(--fc2-space-xxs) + var(--fc2-border-width)) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-settings-grid {
        display: grid;
        grid-template-columns: minmax(0,1.3fr) minmax(320px,0.9fr);
        gap: var(--fc2-space-lg);
        align-items: start;
    }

    .fc2-settings-grid-main,
    .fc2-settings-grid-side {
        display: grid;
        gap: var(--fc2-space-md);
        align-content: start;
    }

    .fc2-portal-actions {
        display: flex;
        align-items: center;
        gap: var(--fc2-space-sm);
        padding: var(--fc2-space-sm) var(--fc2-space-md);
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
    }

    .fc2-settings-tab {
        display: flex;
        flex-direction: column;
        gap: var(--fc2-space-md);
    }

    .fc2-data-container .fc2-enh-form-row {
        flex-direction: row !important;
        justify-content: flex-start;
        align-items: center;
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
        padding: var(--fc2-space-sm) var(--fc2-space-md);
        gap: var(--fc2-space-md);
    }

    .fc2-data-container .fc2-enh-form-row .fc2-enh-label {
        flex: 0 0 140px;
        margin-bottom: 0;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        opacity: var(--fc2-opacity-dim);
    }

    .fc2-data-container .fc2-enh-form-row .fc2-enh-select,
    .fc2-data-container .fc2-enh-form-row .fc2-enh-input,
    .fc2-data-container .fc2-enh-form-row .fc2-input-group {
        flex: 1;
        width: auto;
    }

    .fc2-data-container .fc2-enh-form-row:last-child {
        border-bottom: none;
    }

    .fc2-settings-card-grid {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        width: 100%;
    }

    .fc2-settings-advanced-block {
        margin-top: var(--fc2-space-base);
        padding-top: var(--fc2-space-base);
        border-top: var(--fc2-border-width) dashed rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-settings-advanced-label {
        font-size: var(--fc2-text-xs) !important;
        font-weight: var(--fc2-font-semibold) !important;
        color: var(--fc2-text-muted);
        margin-bottom: var(--fc2-space-sm);
        letter-spacing: 0.02em;
        text-transform: uppercase;
    }

    .fc2-panel-card.warning {
        border-color: rgba(var(--fc2-warn-rgb), 0.2);
    }

    .fc2-panel-card.warning h4 .fc2-icon {
        color: var(--fc2-warn);
    }

    .fc2-settings-empty-state {
        display: none;
        padding: calc(var(--fc2-space-md) + var(--fc2-space-xs));
        border: var(--fc2-border-width) dashed rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
        border-radius: var(--fc2-radius-lg);
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
    }

    .fc2-settings-empty-title {
        font-size: var(--fc2-text-base) !important;
        font-weight: var(--fc2-font-bold) !important;
        color: var(--fc2-text);
        margin-bottom: var(--fc2-space-xs);
    }

    .fc2-settings-empty-hint {
        font-size: var(--fc2-text-xs) !important;
        color: var(--fc2-text-muted);
        line-height: var(--fc2-line-base);
    }

    .fc2-label-dim {
        font-size: var(--fc2-text-xs) !important;
        color: var(--fc2-text-muted);
        opacity: var(--fc2-opacity-dim);
    }

    .fc2-divider {
        height: var(--fc2-border-width);
        background: var(--fc2-border);
        margin: var(--fc2-space-base) 0;
        opacity: 0.6;
    }

    .fc2-grid-actions {
        display: flex;
        flex-wrap: wrap;
        align-items: stretch;
        gap: var(--fc2-space-sm);
    }

    .fc2-grid-actions,
    .fc2-debug-actions,
    .fc2-folder-actions,
    .fc2-sync-result-panel .fc2-card-actions,
    .fc2-auth-section .fc2-card-actions,
    .fc2-data-overview-actions {
        display: flex;
        flex-direction: row;
        align-items: stretch;
        justify-content: flex-start;
        flex-wrap: wrap;
        gap: 0 !important;
        padding: 0;
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-md);
        background: var(--fc2-surface-item);
        box-shadow: var(--fc2-shadow-sm);
        backdrop-filter: blur(var(--fc2-blur));
        -webkit-backdrop-filter: blur(var(--fc2-blur));
        overflow: hidden;
        min-height: var(--fc2-btn-md);
    }

    .fc2-grid-actions > .fc2-enh-btn,
    .fc2-debug-actions > .fc2-enh-btn,
    .fc2-folder-actions > .fc2-enh-btn,
    .fc2-sync-result-panel .fc2-card-actions > .fc2-enh-btn,
    .fc2-auth-section .fc2-card-actions > .fc2-enh-btn,
    .fc2-data-overview-actions > .fc2-enh-btn {
        flex: 1 0 auto;
        min-width: 120px;
        height: var(--fc2-btn-md) !important;
        background: transparent !important;
        border: none !important;
        border-radius: 0 !important;
        margin: 0 !important;
        padding: 0 var(--fc2-space-sm) !important;
        box-shadow: none !important;
        backdrop-filter: none !important;
        font-size: var(--fc2-text-xs) !important;
    }

    .fc2-grid-actions > .fc2-enh-btn:not(:last-child),
    .fc2-debug-actions > .fc2-enh-btn:not(:last-child),
    .fc2-folder-actions > .fc2-enh-btn:not(:last-child),
    .fc2-sync-result-panel .fc2-card-actions > .fc2-enh-btn:not(:last-child),
    .fc2-auth-section .fc2-card-actions > .fc2-enh-btn:not(:last-child),
    .fc2-data-overview-actions > .fc2-enh-btn:not(:last-child) {
        border-right: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
        border-bottom: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
    }

    .fc2-grid-actions > .fc2-enh-btn:hover,
    .fc2-debug-actions > .fc2-enh-btn:hover,
    .fc2-sync-result-panel .fc2-card-actions > .fc2-enh-btn:hover,
    .fc2-auth-section .fc2-card-actions > .fc2-enh-btn:hover,
    .fc2-data-overview-actions > .fc2-enh-btn:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
        transform: none !important;
    }

    .fc2-enh-hint {
        font-size: var(--fc2-text-xs) !important;
        line-height: var(--fc2-line-base);
        padding: var(--fc2-space-sm) var(--fc2-space-md);
        color: var(--fc2-text-muted);
    }

    .fc2-led-group {
        display: inline-flex;
        align-items: center;
        gap: var(--fc2-space-xs);
    }

    .fc2-led-dot {
        width: 8px;
        height: 8px;
        border-radius: var(--fc2-radius-full);
        background: var(--fc2-text-muted);
        display: inline-block;
        flex-shrink: 0;
        transition: background var(--fc2-motion-fast);
    }

    .fc2-led-dot.active {
        background: var(--fc2-success);
        box-shadow: 0 0 6px rgba(var(--fc2-success-rgb), var(--fc2-opacity-dim));
    }

    .fc2-led-label {
        font-size: var(--fc2-text-xs) !important;
        color: var(--fc2-text-dim);
    }

    .fc2-grid-actions.compact {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
        gap: 0;
    }

    .fc2-grid-actions.compact > .fc2-enh-btn {
        min-width: 0;
        height: var(--fc2-btn-sm) !important;
        font-size: var(--fc2-text-xs) !important;
    }

    .fc2-auth-section {
        margin-top: var(--fc2-space-base);
        padding-top: var(--fc2-space-base);
        border-top: var(--fc2-border-width) solid var(--fc2-border);
    }

    .fc2-auth-section .fc2-dim {
        font-size: var(--fc2-text-xs) !important;
        color: var(--fc2-text-muted);
    }

    .fc2-data-overview-grid {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: 0;
    }

    .fc2-data-overview-grid .fc2-stat-item {
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
    }

    .fc2-data-overview-grid .fc2-stat-item:last-child {
        border-bottom: none;
    }

    .fc2-data-overview-sync {
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        gap: var(--fc2-space-xs);
    }

    .fc2-data-overview-meta {
        font-size: var(--fc2-text-xs) !important;
        color: var(--fc2-text-muted);
        line-height: 1.4;
    }

    .fc2-data-overview-actions {
        flex-wrap: nowrap;
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-full);
        margin: var(--fc2-space-base) 0;
        height: var(--fc2-btn-md);
    }

    .fc2-sync-summary {
        display: flex;
        align-items: flex-start;
        gap: var(--fc2-space-base);
        padding: var(--fc2-space-sm) var(--fc2-space-md);
        background: transparent;
        border: none;
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
    }

    .fc2-sync-result-panel {
        display: flex;
        flex-direction: column;
        gap: 0;
        padding: 0;
        margin: 0;
        background: transparent;
        border: none;
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
    }

    .fc2-sync-result-body {
        padding: var(--fc2-space-sm) var(--fc2-space-md);
        display: flex;
        flex-direction: column;
        gap: var(--fc2-space-xs);
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
    }

    .fc2-sync-result-summary {
        font-size: var(--fc2-text-sm) !important;
        font-weight: var(--fc2-font-semibold) !important;
        color: var(--fc2-text);
        line-height: 1.4;
    }

    .fc2-sync-conflicts-list {
        margin-top: var(--fc2-space-xs);
        display: flex;
        flex-direction: column;
        gap: var(--fc2-space-xxs);
    }

    .fc2-sync-summary-copy {
        display: flex;
        flex-direction: column;
        gap: var(--fc2-space-xxs);
        min-width: 0;
    }

    .fc2-sync-summary-title {
        font-size: var(--fc2-text-sm) !important;
        font-weight: var(--fc2-font-bold) !important;
        color: var(--fc2-text);
    }

    .fc2-sync-summary-detail {
        font-size: var(--fc2-text-xs) !important;
        color: var(--fc2-text-muted);
        line-height: 1.4;
    }

    .fc2-sync-status-pill {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: var(--fc2-btn-lg);
        padding: var(--fc2-space-xxs) var(--fc2-space-sm);
        border-radius: var(--fc2-radius-full);
        border: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        font-size: var(--fc2-text-xs) !important;
        font-weight: var(--fc2-font-bold) !important;
        white-space: nowrap;
        text-transform: uppercase;
        letter-spacing: 0.02em;
    }

    .fc2-sync-status-pill.idle {
        background: var(--fc2-surface-item);
        color: var(--fc2-text-muted);
        border-color: var(--fc2-border);
    }

    .fc2-sync-status-pill.syncing {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-primary);
        border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
    }

    .fc2-sync-status-pill.success {
        background: rgba(var(--fc2-success-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-success);
        border-color: rgba(var(--fc2-success-rgb), var(--fc2-opacity-muted));
    }

    .fc2-sync-status-pill.error {
        background: rgba(var(--fc2-danger-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-danger);
        border-color: rgba(var(--fc2-danger-rgb), var(--fc2-opacity-muted));
    }

    .fc2-sync-status-pill.conflict {
        background: rgba(var(--fc2-warn-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-warn);
        border-color: rgba(var(--fc2-warn-rgb), var(--fc2-opacity-muted));
    }

    /* ============================================================
       DEBUG TAB
       ============================================================ */

    .fc2-debug-container {
        display: flex;
        flex-direction: column;
        gap: var(--fc2-space-md);
        height: 100%;
    }

    .fc2-debug-header {
        display: flex;
        flex-direction: column;
        gap: var(--fc2-space-sm);
        flex-shrink: 0;
        padding: var(--fc2-space-md) var(--fc2-space-md);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-md);
        background: linear-gradient(180deg, rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost)), rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)));
    }

    .fc2-debug-actions {
        display: flex;
        gap: var(--fc2-space-sm);
        flex-wrap: wrap;
    }

    .fc2-debug-filters {
        display: flex;
        align-items: center;
        gap: var(--fc2-space-md);
        flex-wrap: wrap;
        padding-top: var(--fc2-space-md);
        border-top: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-debug-filters .fc2-enh-input {
        flex: 1 1 220px;
        max-width: 320px;
    }

    .fc2-log-list-container {
        flex: 1;
        overflow-y: auto;
        min-height: 0;
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-sm);
        background: rgba(var(--fc2-black-rgb), var(--fc2-opacity-muted));
        padding: var(--fc2-space-sm);
        scrollbar-width: thin;
    }

    .fc2-log-item {
        display: flex;
        align-items: flex-start;
        gap: var(--fc2-space-sm);
        padding: var(--fc2-space-xs) var(--fc2-space-sm);
        border-bottom: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
        font-family: var(--fc2-font-mono) !important;
        font-size: var(--fc2-text-xs) !important;
        line-height: var(--fc2-line-base) !important;
        transition: background var(--fc2-motion-fast);
    }

    .fc2-log-item:hover { background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost)); }
    .fc2-log-item:last-child { border-bottom: none; }

    .fc2-log-item.level-error { border-left: calc(var(--fc2-border-width) * 3) solid var(--fc2-danger); }
    .fc2-log-item.level-warn { border-left: calc(var(--fc2-border-width) * 3) solid var(--fc2-warn); }
    .fc2-log-item.level-info { border-left: calc(var(--fc2-border-width) * 3) solid var(--fc2-text-dim); }
    .fc2-log-item.level-success { border-left: calc(var(--fc2-border-width) * 3) solid var(--fc2-success); }
    .fc2-log-item.level-debug { border-left: calc(var(--fc2-border-width) * 3) solid var(--fc2-text-muted); }

    .fc2-log-time { color: var(--fc2-text-muted); white-space: nowrap; flex-shrink: 0; }
    .fc2-log-level { font-weight: var(--fc2-font-semibold) !important; white-space: nowrap; flex-shrink: 0; min-width: var(--fc2-btn-lg); }
    .fc2-log-module { color: var(--fc2-text-dim); white-space: nowrap; flex-shrink: 0; }
    .fc2-log-msg { color: var(--fc2-text); word-break: break-word; flex: 1; }

    .fc2-log-trace {
        font-size: var(--fc2-text-xs) !important;
        color: var(--fc2-text-muted);
        font-family: var(--fc2-font-mono);
        margin-left: auto;
        white-space: nowrap;
        flex-shrink: 0;
    }

    .level-error .fc2-log-level { color: var(--fc2-danger); }
    .level-warn .fc2-log-level { color: var(--fc2-warn); }
    .level-info .fc2-log-level { color: var(--fc2-text-dim); }
    .level-success .fc2-log-level { color: var(--fc2-success); }
    .level-debug .fc2-log-level { color: var(--fc2-text-muted); }

    .fc2-log-payload {
        margin: var(--fc2-space-xs) 0 0 0;
        padding: var(--fc2-space-sm);
        background: rgba(var(--fc2-black-rgb), var(--fc2-opacity-muted));
        border-radius: var(--fc2-radius-sm);
        font-size: var(--fc2-text-xs) !important;
        color: var(--fc2-text-dim);
        overflow-x: auto;
        white-space: pre-wrap;
        word-break: break-all;
    }

    .fc2-log-payload-toggle {
        padding: var(--fc2-space-xxs) var(--fc2-space-sm);
        background: var(--fc2-surface-item);
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-sm);
        color: var(--fc2-text-dim);
        font-size: var(--fc2-text-xs) !important;
        cursor: pointer;
        margin-left: auto;
        flex-shrink: 0;
        transition: all var(--fc2-motion-fast);
    }

    .fc2-log-payload-toggle:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-text);
    }

    /* ============================================================
       SCRAPER DIAGNOSIS
       ============================================================ */

    .fc2-stat-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: var(--fc2-space-sm) var(--fc2-space-md);
        width: 100%;
        box-sizing: border-box;
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
    }

    .fc2-stat-item:last-child {
        border-bottom: none;
    }

    .fc2-stat-value {
        font-size: var(--fc2-text-sm) !important;
        font-weight: var(--fc2-font-bold) !important;
        color: var(--fc2-primary);
        order: 2;
    }

    .fc2-stat-unit {
        font-size: var(--fc2-text-xs) !important;
        color: var(--fc2-text-muted);
        margin-left: var(--fc2-space-xxs);
    }

    .fc2-stat-label {
        font-size: var(--fc2-text-sm) !important;
        font-weight: var(--fc2-font-semibold) !important;
        color: var(--fc2-text-dim);
        order: 1;
        opacity: var(--fc2-opacity-strong);
    }

    /* Scraper Diagnosis */
    .fc2-scraper-list {
        display: flex;
        flex-direction: column;
        gap: 0;
        border: var(--fc2-border-width) solid var(--fc2-border);
        border-radius: var(--fc2-radius-md);
        overflow: hidden;
    }

    .fc2-scraper-row {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: var(--fc2-space-sm) var(--fc2-space-base);
        background: transparent;
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
        transition: all var(--fc2-motion-fast) var(--fc2-ease-standard);
    }
    
    .fc2-scraper-row:last-child {
        border-bottom: none;
    }

    .fc2-scraper-row:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-scraper-info {
        display: flex;
        align-items: center;
        gap: var(--fc2-space-sm);
    }

    .fc2-scraper-label {
        font-size: var(--fc2-text-sm) !important;
        font-weight: var(--fc2-font-semibold) !important;
        color: var(--fc2-text);
    }

    .fc2-scraper-url {
        font-size: var(--fc2-text-xs);
        color: var(--fc2-text-muted);
        transition: color var(--fc2-motion-base);
    }

    .fc2-scraper-url:hover {
        color: var(--fc2-primary);
    }

    .fc2-status-indicator {
        font-size: var(--fc2-text-xs) !important;
        font-weight: var(--fc2-font-bold) !important;
        padding: var(--fc2-space-xxs) var(--fc2-space-base);
        border-radius: var(--fc2-radius-full);
        text-transform: uppercase;
        border: var(--fc2-border-width) solid transparent;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    .fc2-status-indicator.ok {
        background: rgba(var(--fc2-success-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-success);
    }

    .fc2-status-indicator.cf {
        background: rgba(var(--fc2-warn-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-warn);
    }

    .fc2-status-indicator.error {
        background: rgba(var(--fc2-danger-rgb), var(--fc2-opacity-subtle));
        color: var(--fc2-danger);
    }

    .fc2-status-indicator.429 {
        background: rgba(var(--fc2-warn-rgb), var(--fc2-opacity-muted));
        color: var(--fc2-warn);
    }

    /* ============================================================
       ABOUT TAB
       ============================================================ */

    .fc2-about-tab {
        display: flex;
        flex-direction: column;
        gap: var(--fc2-space-md);
    }

    .fc2-about-header {
        text-align: center;
        padding: var(--fc2-space-xl) 0 var(--fc2-space-md);
    }

    .fc2-version-badge {
        display: inline-block;
        margin-top: var(--fc2-space-sm);
        padding: var(--fc2-space-xs) var(--fc2-space-md);
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
        border: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-muted));
        border-radius: var(--fc2-radius-full);
        font-size: var(--fc2-text-xs) !important;
        font-weight: var(--fc2-font-bold) !important;
        color: var(--fc2-text);
        letter-spacing: 0.1em;
        text-transform: uppercase;
    }

    .fc2-about-desc {
        margin-top: var(--fc2-space-base);
        color: var(--fc2-text-dim);
        font-size: var(--fc2-text-base) !important;
        line-height: 1.6 !important;
        max-width: 500px;
        margin-left: auto;
        margin-right: auto;
    }

    .fc2-about-content {
        font-size: var(--fc2-text-sm) !important;
        line-height: var(--fc2-line-base) !important;
        color: var(--fc2-text-dim);
    }

    .fc2-about-content.dmca {
        color: var(--fc2-text-muted);
    }

    .fc2-about-footer {
        text-align: center;
        padding: var(--fc2-space-lg) 0;
        font-size: var(--fc2-text-sm) !important;
        color: var(--fc2-text-muted);
    }

    .fc2-link {
        color: var(--fc2-primary);
        text-decoration: none;
        transition: opacity var(--fc2-motion-fast);
    }

    .fc2-link:hover { opacity: 0.7; }

    /* ============================================================
       DATA TAB PREVIEW & LOGS
       ============================================================ */
    .fc2-backup-preview-lines {
        display: flex;
        flex-direction: column;
        background: rgba(var(--fc2-black-rgb), var(--fc2-opacity-muted));
        max-height: 240px;
        overflow-y: auto;
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
        scrollbar-width: thin;
    }

    .fc2-backup-preview-line {
        padding: var(--fc2-space-xs) var(--fc2-space-md);
        font-size: var(--fc2-text-xs) !important;
        font-family: var(--fc2-font-mono);
        color: var(--fc2-text-dim);
        border-bottom: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .fc2-backup-preview-line:last-child {
        border-bottom: none;
    }

    @media(max-width:768px) {
        .fc2-settings-grid { grid-template-columns: 1fr; }
        .fc2-debug-filters .fc2-enh-input {
            flex-basis: 100%;
            max-width: none;
        }
        .fc2-settings-card-grid { grid-template-columns: 1fr; }
        .fc2-grid-actions { flex-direction: column; }
        .fc2-grid-actions,
        .fc2-debug-actions,
        .fc2-folder-actions,
        .fc2-sync-result-panel .fc2-card-actions,
        .fc2-auth-section .fc2-card-actions,
        .fc2-data-overview-actions {
            padding: var(--fc2-space-sm);
        }
        .fc2-grid-actions,
        .fc2-debug-actions,
        .fc2-sync-result-panel .fc2-card-actions,
        .fc2-auth-section .fc2-card-actions,
        .fc2-data-overview-actions {
            flex-direction: column;
        }
        .fc2-grid-actions > .fc2-enh-btn,
        .fc2-debug-actions > .fc2-enh-btn,
        .fc2-sync-result-panel .fc2-card-actions > .fc2-enh-btn,
        .fc2-auth-section .fc2-card-actions > .fc2-enh-btn,
        .fc2-data-overview-actions > .fc2-enh-btn {
            flex-basis: auto;
        }
        .fc2-portal-actions { flex-wrap: wrap; }
        .fc2-data-overview-meta,
        .fc2-sync-result-summary,
        .fc2-sync-summary-detail {
            overflow-wrap: anywhere;
        }
        .fc2-sync-status-pill,
        .fc2-log-level {
            white-space: normal;
        }
        .fc2-log-level { min-width: 0; }
        .fc2-debug-filters { gap: var(--fc2-space-sm); }
        .fc2-log-item { flex-wrap: wrap; gap: var(--fc2-space-xs); }
        .fc2-log-time, .fc2-log-module { display: none; }
    }

    /* Color Picker */
    .fc2-color-picker {
        display: flex;
        flex-wrap: wrap;
        gap: var(--fc2-space-sm);
        padding: var(--fc2-space-xxs) 0;
    }

    .fc2-color-option {
        width: var(--fc2-space-lg);
        height: var(--fc2-space-lg);
        border-radius: var(--fc2-radius-full);
        border: calc(var(--fc2-border-width) * 2) solid transparent;
        background-color: var(--option-color);
        cursor: pointer;
        transition: all var(--fc2-motion-fast) var(--fc2-ease-out);
        padding: 0;
        position: relative;
        box-shadow: var(--fc2-shadow-sm);
    }

    .fc2-color-option:hover {
        transform: scale(1.1);
        box-shadow: var(--fc2-shadow-md);
    }

    .fc2-color-option.is-active {
        border-color: var(--fc2-text-bright);
        transform: scale(1.1);
        box-shadow: 0 0 12px var(--option-color);
    }
    
    .fc2-color-option.is-active::after {
        content: '';
        position: absolute;
        top: calc(-1 * var(--fc2-space-xs));
        left: calc(-1 * var(--fc2-space-xs));
        right: calc(-1 * var(--fc2-space-xs));
        bottom: calc(-1 * var(--fc2-space-xs));
        border: var(--fc2-border-width) solid var(--option-color);
        border-radius: var(--fc2-radius-full);
        opacity: var(--fc2-opacity-dim);
    }

    /* Fix for light colors on dark background */
    .fc2-color-option[data-value="#ffffff"].is-active {
        border-color: var(--fc2-text);
        box-shadow: 0 0 12px rgba(255, 255, 255, 0.4);
    }
`;
var tabs = (_C) => `
    /* ============================================================
       SETTINGS NAVIGATION
       ============================================================ */

    .fc2-enh-settings-primary-nav {
        display:flex;
        width: 240px;
        flex-direction:column;
        gap:0;
        padding:var(--fc2-space-sm) 0;
        border-right: var(--fc2-border-width) solid var(--fc2-border);
        background:rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
        overflow-y:auto;
        flex-shrink:0;
    }

    .fc2-enh-tab-btn {
        display:flex;
        align-items:center;
        gap: var(--fc2-space-sm);
        width:100%;
        padding: var(--fc2-space-base) var(--fc2-space-md);
        background:transparent;
        color:var(--fc2-text);
        opacity: var(--fc2-opacity-dim);
        border:none;
        border-bottom: var(--fc2-border-width) solid var(--fc2-border);
        border-radius:0;
        font-size: var(--fc2-text-sm);
        font-weight: var(--fc2-font-semibold);
        text-align:left;
        transition:all var(--fc2-motion-fast) var(--fc2-ease-standard);
        cursor:pointer;
        position:relative;
    }

    .fc2-enh-tab-btn:last-child {
        border-bottom:none;
    }

    .fc2-enh-tab-btn:hover {
        background:rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        color:var(--fc2-text);
    }

    .fc2-enh-tab-btn:focus-visible {
        outline:none;
        box-shadow: inset 0 0 0 calc(var(--fc2-space-xxs) + var(--fc2-border-width)) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-enh-tab-btn.active {
        background:rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        color:var(--fc2-primary);
        font-weight: var(--fc2-font-semibold);
        opacity:1;
        box-shadow:inset calc(var(--fc2-border-width) * 3) 0 0 var(--fc2-primary);
    }

    .fc2-enh-tab-btn .fc2-icon {
        font-size:1.1em;
        opacity:0.8;
    }

    .fc2-enh-tab-btn.active .fc2-icon {
        opacity:1;
    }

    .fc2-enh-tab-btn:active {
        transform:scale(0.96);
    }

    .fc2-tab-content-wrapper > :is(.fc2-dashboard-container, .fc2-data-container, .fc2-settings-tab) {
        display:flex;
        flex-direction:column;
        gap: var(--fc2-space-md);
        min-width:0;
        min-height:100%;
    }

    .fc2-tab-content-wrapper:not(.full-width) > :is(.fc2-dashboard-container, .fc2-data-container, .fc2-settings-tab) {
        width:min(100%, 1180px);
        margin:0 auto;
    }

    /* Tab Content Transitions */
    .fc2-tab-content-wrapper {
        height:100%;
        overflow-y:auto;
        padding: var(--fc2-space-lg) var(--fc2-space-lg) var(--fc2-space-lg);
        scrollbar-width:thin;
        transition: opacity var(--fc2-motion-slow) var(--fc2-ease-out), transform var(--fc2-motion-slow) var(--fc2-ease-out);
        opacity: 0;
        transform: translateY(var(--fc2-space-md)) scale(0.98);
        will-change: opacity, transform;
    }

    .fc2-tab-content-wrapper.fc2-entering {
        opacity: 1;
        transform: translateY(0) scale(1);
    }

    .fc2-tab-content-wrapper.fc2-leaving {
        opacity: 0;
        transform: translateY(calc(-1 * var(--fc2-space-md))) scale(1.02);
        pointer-events: none;
    }

    /* Mobile Bottom Navigation */
    @media(max-width:768px) {
        .fc2-enh-settings-primary-nav {
            width:100%;
            flex-direction:row;
            flex-wrap:wrap;
            overflow-x:visible;
            border-right:none;
            border-bottom:var(--fc2-border-width) solid var(--fc2-border);
            padding:0.6rem 0.75rem;
            gap:0.5rem;
        }

        .fc2-enh-nav-label {
            display:none;
        }

        .fc2-tab-content-wrapper {
            height:auto;
            overflow:visible;
            padding:1rem 1rem calc(1rem + env(safe-area-inset-bottom));
        }

        .fc2-tab-content-wrapper > :is(.fc2-dashboard-container, .fc2-data-container, .fc2-settings-tab) {
            gap:0.85rem;
        }

        .fc2-enh-tab-btn {
            flex-direction:row;
            gap: var(--fc2-space-sm);
            padding: var(--fc2-space-sm) var(--fc2-space-base);
            font-size: var(--fc2-text-xs);
            justify-content:center;
            border-radius:var(--fc2-radius-full);
            flex:1 1 auto;
            min-width:0;
            text-align:center;
            white-space:normal;
            line-height:1;
            background:rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost));
            border:var(--fc2-border-width) solid var(--fc2-border);
        }

        .fc2-enh-tab-btn.active {
            background:var(--fc2-primary);
            color:var(--fc2-on-primary);
            border-color:transparent;
        }

        .fc2-enh-tab-btn .fc2-icon {
            font-size:1.4em;
            margin-bottom:var(--fc2-space-xxs);
        }
    }
`;
var toast = (_C) => `
    /* ============================================================
       TOAST NOTIFICATIONS
       ============================================================ */

    .fc2-toast-container {
        position:fixed;
        top:var(--fc2-space-md);
        right:var(--fc2-space-md);
        z-index:var(--fc2-z-toast);
        display:flex;
        flex-direction:column;
        gap:var(--fc2-space-sm);
        pointer-events:none;
    }

    .fc2-toast-item {
        position:relative;
        display:flex;
        align-items:center;
        min-width:280px;
        max-width:420px;
        padding:var(--fc2-space-sm) var(--fc2-space-md);
        background:var(--fc2-surface-float);
        color:var(--fc2-text);
        border-radius:var(--fc2-radius-md);
        box-shadow:var(--fc2-shadow-lg);
        font-size: var(--fc2-text-base);
        font-weight: var(--fc2-font-medium);
        backdrop-filter:blur(var(--fc2-blur));
        -webkit-backdrop-filter:blur(var(--fc2-blur));
        border: var(--fc2-border-width) solid var(--fc2-border);
        opacity:0;
        pointer-events:auto;
        animation:fc2-toast-in var(--fc2-motion-base) var(--fc2-ease-out) forwards;
    }

    .fc2-toast-item.hiding {
        animation:fc2-toast-out var(--fc2-motion-slow) var(--fc2-ease-standard) forwards;
    }

    .fc2-toast-icon {
        display:flex;
        align-items:center;
        justify-content:center;
        font-size: var(--fc2-text-xl);
        margin-right:var(--fc2-space-sm);
        flex-shrink:0;
    }

    .fc2-toast-content {
        flex-grow:1;
        line-height:1.4;
        word-break:break-word;
        overflow:hidden;
    }

    .fc2-toast-close {
        background:none;
        border:none;
        color:var(--fc2-text-dim);
        cursor:pointer;
        padding:var(--fc2-space-xs);
        margin-left:var(--fc2-space-sm);
        border-radius:var(--fc2-radius-full);
        transition:all var(--fc2-motion-fast);
        display:flex;
        align-items:center;
    }

    .fc2-toast-close:hover {
        background:var(--fc2-surface-item);
        color:var(--fc2-text);
    }

    .fc2-toast-close:focus-visible {
        outline:none;
        box-shadow:0 0 0 calc(var(--fc2-space-xxs) + var(--fc2-border-width)) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
    }

    .fc2-toast-progress {
        position:absolute;
        bottom:0;
        left:0;
        height:var(--fc2-space-xxs);
        width:100%;
        transform-origin:left;
        background:var(--fc2-toast-color, var(--fc2-primary));
        animation:fc2-toast-shrink var(--fc2-toast-duration, 3000ms) linear forwards;
    }

    .toast-success { border-left:var(--fc2-space-xs) solid var(--fc2-success); }
    .toast-success .fc2-toast-icon { color:var(--fc2-success); }
    .toast-error { border-left:var(--fc2-space-xs) solid var(--fc2-danger); }
    .toast-error .fc2-toast-icon { color:var(--fc2-danger); }
    .toast-warn { border-left:var(--fc2-space-xs) solid var(--fc2-warn); }
    .toast-warn .fc2-toast-icon { color:var(--fc2-warn); }
    .toast-info { border-left:var(--fc2-space-xs) solid var(--fc2-info); }
    .toast-info .fc2-toast-icon { color:var(--fc2-info); }

    @keyframes fc2-toast-shrink {
        from { width:100%; }
        to { width:0%; }
    }

    .fc2-toast-action {
        margin-left:var(--fc2-space-sm);
        padding:var(--fc2-space-xxs) var(--fc2-space-sm);
        background:rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));
        border:var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb),var(--fc2-opacity-muted));
        border-radius:var(--fc2-radius-sm);
        color:var(--fc2-primary);
        font-size:var(--fc2-text-xs);
        font-weight: var(--fc2-font-semibold);
        cursor:pointer;
        transition:all var(--fc2-motion-fast);
    }

    .fc2-toast-action:hover {
        background:var(--fc2-primary);
        color:var(--fc2-on-primary);
        border-color:transparent;
    }
`;
var toolbar = (_C) => `
    /* ============================================================
       PLAYER TOOLBAR (DESKTOP)
       ============================================================ */

    .fc2-enh-toolbar {
        width: 100%;
        max-width: min(100%, 1180px);
        margin: var(--fc2-space-md) auto;
        background: transparent !important;
        border: var(--fc2-border-width) solid var(--fc2-border) !important;
        border-radius: var(--fc2-radius-lg) !important;
        backdrop-filter: blur(var(--fc2-blur)) !important;
        -webkit-backdrop-filter: blur(var(--fc2-blur)) !important;
    }

    .fc2-enh-toolbar--detail {
        margin: var(--fc2-space-lg) auto;
        max-width: 100%;
    }

    .fc2-enh-toolbar .fc2-info-area {
        display: grid !important;
        grid-template-columns: auto 1fr auto !important;
        align-items: center !important;
        width: 100% !important;
        padding: 0 !important;
        gap: var(--fc2-space-md) !important;
    }

    .fc2-enh-toolbar[data-layout="without-actress"] .fc2-info-area {
        grid-template-columns: auto 1fr !important;
    }

    .fc2-enh-toolbar[data-layout="with-actress"] .fc2-info-area {
        grid-template-columns: auto 1fr auto !important;
    }

    .fc2-enh-toolbar .fc2-toolbar-section-controls {
        grid-column: 1;
        display: flex;
        align-items: center;
        justify-content: flex-start;
        min-width: 0;
        width: auto !important;
    }

    .fc2-enh-toolbar .fc2-toolbar-section-links {
        grid-column: 3;
        display: flex;
        align-items: center;
        justify-content: flex-end;
        min-width: 0;
        width: auto !important;
    }

    .fc2-enh-toolbar .fc2-card-top-right-controls,
    .fc2-enh-toolbar .fc2-resource-links-container {
        position: static !important;
        display: flex !important;
        flex-direction: row !important;
        flex-wrap: nowrap !important;
        align-items: stretch !important;
        background: var(--fc2-surface-item) !important;
        border: var(--fc2-border-width) solid var(--fc2-border) !important;
        border-radius: var(--fc2-radius-full) !important;
        padding: 0 !important;
        box-shadow: var(--fc2-shadow-sm) !important;
        overflow: hidden !important;
        backdrop-filter: blur(var(--fc2-blur)) !important;
        -webkit-backdrop-filter: blur(var(--fc2-blur)) !important;
        height: var(--fc2-btn-md) !important;
        gap: 0 !important;
    }

    .fc2-enh-toolbar .fc2-btn-actress {
        grid-column: 2;
        justify-self: center;
        margin: 0 !important;
        height: var(--fc2-btn-md) !important;
        line-height: var(--fc2-line-base) !important;
        padding: 0 var(--fc2-space-lg) !important;
        background: var(--fc2-surface-item) !important;
        border: var(--fc2-border-width) solid var(--fc2-border) !important;
        border-radius: var(--fc2-radius-full) !important;
        box-shadow: var(--fc2-shadow-sm) !important;
        backdrop-filter: blur(var(--fc2-blur)) !important;
        -webkit-backdrop-filter: blur(var(--fc2-blur)) !important;
        white-space: nowrap !important;
        overflow: hidden !important;
        text-overflow: ellipsis !important;
        max-width: min(100%, 28rem) !important;
        text-align: center !important;
    }

    .fc2-enh-toolbar .fc2-card-top-right-controls > *,
    .fc2-enh-toolbar .fc2-resource-links-container > * {
        background: transparent !important;
        border: none !important;
        border-radius: 0 !important;
        box-shadow: none !important;
        backdrop-filter: none !important;
        -webkit-backdrop-filter: none !important;
        height: 100% !important;
        padding: 0 var(--fc2-space-md) !important;
        margin: 0 !important;
        display: inline-flex !important;
        align-items: center !important;
        justify-content: center !important;
        color: var(--fc2-text) !important;
        transition: background var(--fc2-motion-fast), transform var(--fc2-motion-fast);
    }

    .fc2-enh-toolbar .fc2-resource-links-container > .icon-only {
        padding: 0 var(--fc2-space-sm) !important;
        width: var(--fc2-btn-lg) !important;
    }

    .fc2-enh-toolbar .fc2-card-top-right-controls .fc2-id-badge {
        font-size: var(--fc2-text-sm) !important;
    }

    .fc2-enh-toolbar .fc2-card-top-right-controls > *:not(:last-child),
    .fc2-enh-toolbar .fc2-resource-links-container > *:not(:last-child) {
        border-right: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
    }

    .fc2-enh-toolbar .fc2-card-top-right-controls > *:hover,
    .fc2-enh-toolbar .fc2-resource-links-container > *:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
    }
    
    .fc2-enh-toolbar .fc2-btn-actress:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
        border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
        transform: translateY(calc(-1 * var(--fc2-border-width))) !important;
    }

    .fc2-enh-toolbar .fc2-resource-links-container .fc2-magnet {
        animation: none !important;
        color: var(--fc2-primary) !important;
    }
    
    .fc2-enh-toolbar .fc2-resource-links-container .fc2-magnet:hover {
        background: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
    }

    @media (max-width: 920px) {
        .fc2-enh-toolbar .fc2-info-area {
            grid-template-columns: auto minmax(0, 1fr) auto !important;
            gap: var(--fc2-space-sm) !important;
        }
        .fc2-enh-toolbar .fc2-toolbar-section-controls,
        .fc2-enh-toolbar .fc2-toolbar-section-links {
            width: auto !important;
        }
        .fc2-enh-toolbar .fc2-btn-actress {
            max-width: min(100%, 20rem) !important;
        }
    }

    @media (max-width: 768px) and (hover: hover), (max-width: 768px) and (pointer: fine) {
        .fc2-enh-toolbar .fc2-info-area {
            grid-template-columns: minmax(0, 1fr) auto !important;
        }
        .fc2-enh-toolbar .fc2-toolbar-section-controls {
            grid-row: 1 !important;
        }
        .fc2-enh-toolbar .fc2-toolbar-section-links {
            grid-row: 1 !important;
        }
        .fc2-enh-toolbar .fc2-btn-actress {
            grid-column: 1 / -1 !important;
            grid-row: 2 !important;
            justify-self: center !important;
            height: 34px !important;
            line-height: 32px !important;
            font-size: var(--fc2-text-xs) !important;
            width: fit-content !important;
            max-width: min(100%, 18rem) !important;
            padding: 0 var(--fc2-space-md) !important;
        }
    }

    @media (max-width: 768px) {
        .fc2-enh-toolbar .fc2-info-area {
            grid-template-columns: minmax(0, 1fr) auto !important;
        }
        .fc2-enh-toolbar .fc2-btn-actress {
            grid-column: 1 / -1 !important;
            grid-row: 2 !important;
            height: var(--fc2-btn-sm) !important;
            line-height: var(--fc2-line-base) !important;
            font-size: var(--fc2-text-xs) !important;
            max-width: min(100%, 18rem) !important;
        }
    }
`;
var getComponentStyles = (C) => `
    ${base()}
    ${toast()}
    ${fab()}
    ${card(C)}
    ${button(C)}
    ${form()}
    ${tabs()}
    ${settings()}
    ${modal()}
    ${actionsheet()}
    
    /* Refactored Blocks */
    ${common()}
    ${toolbar()}
    ${gallery()}
    ${batch()}
`;
var mkIcon = (svg) => UIUtils.icon(svg);
var buildWorkspaceButtons = (tabsConfig, onTabSelect) => tabsConfig.map((tab, index) => h("button", {
	className: "fc2-enh-tab-btn fc2-enh-workspace-btn",
	role: "tab",
	"aria-selected": index === 0 ? "true" : "false",
	tabindex: index === 0 ? "0" : "-1",
	"data-tab": tab.id,
	"data-settings-category": tab.category,
	onclick: () => onTabSelect(tab.id)
}, mkIcon(tab.icon), tab.navLabel));
var handleTablistKeydown = (event, navContainer, onTabSelect) => {
	const buttons = Array.from(navContainer.querySelectorAll("button[role=\"tab\"]"));
	const current = buttons.findIndex((btn) => btn === document.activeElement || btn.contains(document.activeElement));
	if (current < 0) return;
	let next = -1;
	if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (current + 1) % buttons.length;
	else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (current - 1 + buttons.length) % buttons.length;
	else if (event.key === "Home") next = 0;
	else if (event.key === "End") next = buttons.length - 1;
	if (next >= 0) {
		event.preventDefault();
		const target = buttons[next];
		if (!target) return;
		buttons.forEach((btn, i) => {
			btn.setAttribute("tabindex", i === next ? "0" : "-1");
			btn.setAttribute("aria-selected", i === next ? "true" : "false");
		});
		target.focus();
		const tabId = target.dataset.tab;
		if (tabId) onTabSelect(tabId);
	}
};
var createSettingsPanelView = ({ tabsConfig, onTabSelect, onHide }) => {
	const panel = h("div", {
		id: DOM_IDS.SETTINGS_HOST,
		className: "fc2-is-hidden"
	});
	const container = h("div", { id: DOM_IDS.SETTINGS_CONTAINER });
	const shadow = container.attachShadow({ mode: "open" });
	shadow.appendChild(h("style", {}, getComponentStyles(Config.CLASSES)));
	const header = h("div", { className: "fc2-enh-settings-header" }, h("div", { className: "fc2-enh-settings-heading" }, h("h2", {}, t("managementCenter"))), h("div", { className: "fc2-header-actions" }, h("button", {
		className: "fc2-close-btn",
		onclick: () => onHide(),
		title: t("btnCancel") || "Close"
	}, mkIcon(IconXmark))));
	const workspaceNav = h("div", {
		className: "fc2-enh-settings-primary-nav",
		id: "tab-buttons",
		role: "tablist"
	}, ...buildWorkspaceButtons(tabsConfig, onTabSelect));
	workspaceNav.addEventListener("keydown", (e) => handleTablistKeydown(e, workspaceNav, onTabSelect));
	const panelInner = h("div", {
		className: "fc2-enh-settings-panel has-shadow-isolate",
		tabindex: "-1"
	}, header, h("div", { className: "fc2-enh-settings-body" }, workspaceNav, h("div", {
		className: "fc2-enh-settings-content",
		id: DOM_IDS.TAB_CONTENT
	})));
	const backdrop = h("div", {
		className: "fc2-enh-modal-backdrop",
		onclick: () => onHide()
	});
	shadow.append(backdrop, panelInner);
	panel.appendChild(container);
	const focusFirstInPanel = () => {
		(panelInner.querySelector("button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])") ?? panelInner).focus();
	};
	new MutationObserver(() => {
		if (!panel.classList.contains("fc2-is-hidden")) requestAnimationFrame(focusFirstInPanel);
	}).observe(panel, {
		attributes: true,
		attributeFilter: ["class"]
	});
	return {
		panel,
		shadow
	};
};
var createDataTabDefinition = () => ({
	id: "data",
	title: t("tabData"),
	navLabel: t("tabData"),
	category: "sync",
	entrypoints: ["sync-center"],
	icon: IconDatabase,
	render: async (shadow, switchTab) => {
		const { renderDataTab } = await module.import('./DataTab-bfpDUmRQ-C_bNm5Gc.js');
		return renderDataTab(shadow, switchTab);
	},
	onUnmount: () => {
		module.import('./DataTab-bfpDUmRQ-C_bNm5Gc.js').then(({ onUnmount }) => onUnmount());
	}
});
var createSettingsTabDefinition = () => ({
	id: "settings",
	title: t("tabSettings"),
	navLabel: t("tabSettings"),
	category: "configure",
	entrypoints: ["quick-preferences"],
	icon: IconSliders,
	render: async () => {
		const { renderSettingsTab } = await module.import('./SettingsTab-8ebeRFDG-BDxX9kp7.js');
		return renderSettingsTab();
	},
	onUnmount: () => {
		module.import('./SettingsTab-8ebeRFDG-BDxX9kp7.js').then(({ onUnmount }) => onUnmount());
	}
});
var createDebugTabDefinition = () => ({
	id: "debug",
	title: t("labelTechnicalLogs"),
	navLabel: t("labelTechnicalLogs"),
	category: "diagnose",
	entrypoints: ["support-bundle"],
	icon: IconBolt,
	render: async () => {
		const { renderDebugTab } = await module.import('./DebugTab-BvxslCZw-BzciC75p.js');
		return renderDebugTab();
	},
	onUnmount: () => {
		module.import('./DebugTab-BvxslCZw-BzciC75p.js').then(({ onUnmount }) => onUnmount());
	}
});
var createAboutTabDefinition = () => ({
	id: "about",
	title: t("tabAbout"),
	navLabel: t("tabAbout"),
	category: "info",
	entrypoints: [],
	icon: IconCircleInfo,
	render: async () => {
		const { renderAboutTab } = await module.import('./AboutTab-BYbIrdck-YaaPEqkJ.js');
		return renderAboutTab();
	}
});
var getTabsConfig = () => [
	createDataTabDefinition(),
	createSettingsTabDefinition(),
	createDebugTabDefinition(),
	createAboutTabDefinition()
];
var SETTINGS_PANEL_DEFAULT_TAB_ID = "data";
var createLoadingOverlay = () => h("div", { className: "fc2-loading-overlay" }, h("div", { className: "fc2-loading-spinner" }), h("div", { className: "fc2-loading-text" }, `${t("labelLoading")}...`));
var createErrorState = (tabId) => h("div", { className: "fc2-error-state" }, `${t("labelError")}: ${tabId}`);
var createTabContentWrapper = (tabId, element, isFullWidth) => h("div", {
	className: `fc2-tab-content-wrapper${isFullWidth ? " full-width" : ""}`,
	"data-tab": tabId
}, element);
var shouldInvalidateTabCache = (currentTabId, nextTabId, hasCachedTab) => currentTabId === nextTabId && hasCachedTab;
var resolveTabEnterDelay = (hasOldContent, hasCachedTab) => hasOldContent && hasCachedTab ? TIMING.UI_TRANSITION_FAST : 0;
var waitForTabTransition = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
var log$46 = Logger.scope("SettingsPanel");
var PanelSettingsPanel = class {
	tabsConfig;
	panel = null;
	shadow = null;
	currentTab = SETTINGS_PANEL_DEFAULT_TAB_ID;
	isSwitching = false;
	pendingTabId = null;
	pendingMountTimer = null;
	switchToken = 0;
	tabCache = /* @__PURE__ */ new Map();
	overlay = { close: () => this.hide() };
	constructor(tabsConfig) {
		this.tabsConfig = tabsConfig;
	}
	show(activeTabId) {
		this.ensurePanel();
		if (!this.panel) return;
		const initialTabId = this.resolveInitialTabId(activeTabId);
		if (this.panel.classList.contains("fc2-is-hidden") || this.currentTab !== initialTabId) {
			this.panel.classList.remove("fc2-is-hidden");
			document.body.classList.add("fc2-settings-open");
			document.addEventListener("keydown", this.handleKeyDown);
			OverlayStack.push(this.overlay);
			CoreEvents.emit(AppEvents.PANEL_OPENED, {});
			this.switchTab(initialTabId);
		}
	}
	render(activeTabId) {
		this.show(activeTabId);
	}
	hide(event) {
		if (event) {
			event.preventDefault();
			event.stopPropagation();
		}
		if (!this.panel) return;
		this.panel.classList.add("fc2-is-hidden");
		document.body.classList.remove("fc2-settings-open");
		document.removeEventListener("keydown", this.handleKeyDown);
		if (this.pendingMountTimer !== null) {
			window.clearTimeout(this.pendingMountTimer);
			this.pendingMountTimer = null;
		}
		this.switchToken += 1;
		this.isSwitching = false;
		this.pendingTabId = null;
		OverlayStack.remove(this.overlay);
		CoreEvents.emit(AppEvents.PANEL_CLOSED, {});
	}
	clearCache() {
		this.tabCache.clear();
	}
	get currentTabId() {
		return this.currentTab;
	}
	async switchTab(tabId, intent) {
		this.ensurePanel();
		if (this.isSwitching) {
			this.pendingTabId = tabId;
			if (intent) setPendingSettingsNavigationIntent({
				...intent,
				targetTab: tabId
			});
			return;
		}
		if (intent) setPendingSettingsNavigationIntent({
			...intent,
			targetTab: tabId
		});
		if (shouldInvalidateTabCache(this.currentTab, tabId, this.tabCache.has(tabId))) this.tabCache.delete(tabId);
		const previousTab = this.tabsConfig.find((tab) => tab.id === this.currentTab);
		const nextTab = this.tabsConfig.find((tab) => tab.id === tabId);
		if (!nextTab || !this.shadow) return;
		const token = ++this.switchToken;
		this.isSwitching = true;
		this.pendingTabId = null;
		const contentContainer = this.getContentContainer();
		if (!contentContainer) {
			log$46.error("Tab content container not found");
			this.finishSwitchCycle(token);
			return;
		}
		try {
			this.unmountTab(previousTab);
			const oldContent = contentContainer.querySelector(".fc2-tab-content-wrapper");
			if (oldContent) oldContent.classList.add("fc2-leaving");
			this.updateNavigationState(tabId);
			let content = this.tabCache.get(tabId);
			if (!content) {
				if (oldContent) await waitForTabTransition(TIMING.UI_TRANSITION_FAST);
				content = await this.renderTabContent(nextTab, tabId, contentContainer);
			}
			const enterDelay = resolveTabEnterDelay(Boolean(oldContent), this.tabCache.has(tabId));
			this.mountTabContent(contentContainer, content, tabId, enterDelay, token);
		} catch (error) {
			this.handleTabRenderError(tabId, contentContainer, error, token);
		}
	}
	handleKeyDown = (event) => {
		if (!this.panel || this.panel.classList.contains("fc2-is-hidden")) return;
		if ((event.ctrlKey || event.metaKey) && event.key === "s") {
			event.preventDefault();
			this.saveAndClose();
		}
		if (event.key === "Escape") {
			event.preventDefault();
			this.hide();
		}
	};
	saveAndClose() {
		Toast.show(t("alertSettingsSaved"), "success");
		this.hide();
	}
	ensurePanel() {
		if (this.panel && this.shadow) return;
		const view = createSettingsPanelView({
			tabsConfig: this.tabsConfig,
			onTabSelect: (tabId) => {
				this.switchTab(tabId);
			},
			onHide: (event) => this.hide(event)
		});
		this.panel = view.panel;
		this.shadow = view.shadow;
		document.body.appendChild(this.panel);
	}
	resolveInitialTabId(activeTabId) {
		if (activeTabId) return activeTabId;
		const lastTab = Storage.get(STORAGE_KEYS.LAST_SETTINGS_TAB, "");
		if (lastTab && this.tabsConfig.some((t) => t.id === lastTab)) return lastTab;
		return SETTINGS_PANEL_DEFAULT_TAB_ID;
	}
	getContentContainer() {
		return this.shadow?.getElementById(DOM_IDS.TAB_CONTENT) ?? null;
	}
	updateNavigationState(tabId) {
		this.shadow?.querySelectorAll(".fc2-enh-workspace-btn").forEach((button) => {
			button.classList.toggle("active", button.dataset.tab === tabId);
		});
	}
	unmountTab(tabDef) {
		if (!tabDef?.onUnmount || !this.shadow) return;
		try {
			tabDef.onUnmount(this.shadow);
		} catch (error) {
			log$46.warn(`Tab unmount error: ${tabDef.id}`, error);
		}
	}
	async renderTabContent(tabDef, tabId, contentContainer) {
		contentContainer.textContent = "";
		contentContainer.appendChild(createLoadingOverlay());
		const content = createTabContentWrapper(tabId, await tabDef.render(this.shadow, (nextTabId) => this.switchTab(nextTabId)), Boolean(tabDef.isFullWidth));
		this.tabCache.set(tabId, content);
		return content;
	}
	mountTabContent(contentContainer, content, tabId, enterDelay, token) {
		if (this.pendingMountTimer !== null) window.clearTimeout(this.pendingMountTimer);
		this.pendingMountTimer = window.setTimeout(() => {
			this.pendingMountTimer = null;
			if (token !== this.switchToken || !this.panel || !this.shadow) return;
			contentContainer.textContent = "";
			content.classList.remove("fc2-entering", "fc2-leaving");
			contentContainer.appendChild(content);
			requestAnimationFrame(() => {
				requestAnimationFrame(() => {
					if (token !== this.switchToken) return;
					content.classList.add("fc2-entering");
					this.currentTab = tabId;
					Storage.set(STORAGE_KEYS.LAST_SETTINGS_TAB, tabId);
					this.finishSwitchCycle(token);
				});
			});
		}, enterDelay);
	}
	handleTabRenderError(tabId, contentContainer, error, token) {
		log$46.error(`Failed to render tab: ${tabId}`, error);
		contentContainer.textContent = "";
		contentContainer.appendChild(createErrorState(tabId));
		Toast.show(`${t("labelError")}: ${tabId}`, "error");
		this.finishSwitchCycle(token);
	}
	finishSwitchCycle(token) {
		if (token !== this.switchToken) return;
		this.isSwitching = false;
		const nextTabId = this.pendingTabId;
		this.pendingTabId = null;
		if (nextTabId && nextTabId !== this.currentTab) this.switchTab(nextTabId);
	}
};
var createSettingsPanelController = ({ tabsConfig = getTabsConfig() } = {}) => new PanelSettingsPanel(tabsConfig);
var settingsPanel = null;
/** Lazy: tabs call `t()` and must not construct before DI registers StateService. */
function getSettingsPanel() {
	if (!settingsPanel) settingsPanel = createSettingsPanelController();
	return settingsPanel;
}
/** Compatible facade — all access is deferred until first use (after modules register). */
var SettingsPanel = {
	show: (activeTabId) => getSettingsPanel().show(activeTabId),
	render: (activeTabId) => getSettingsPanel().render(activeTabId),
	hide: () => getSettingsPanel().hide(),
	clearCache: () => getSettingsPanel().clearCache(),
	switchTab: (tabId, intent) => getSettingsPanel().switchTab(tabId, intent),
	get currentTabId() {
		return getSettingsPanel().currentTabId;
	}
};
var bridged = false;
/** Wire settings panel to app events (called once from app/modules, after tokens). */
function initSettingsPanelBridge() {
	if (bridged) return;
	bridged = true;
	CoreEvents.on(AppEvents.OPEN_SETTINGS, () => {
		SettingsPanel.show();
	});
	CoreEvents.on(AppEvents.LANGUAGE_CHANGED, () => {
		SettingsPanel.clearCache();
		if (!document.body.classList.contains("fc2-settings-open")) return;
		SettingsPanel.switchTab(SettingsPanel.currentTabId);
	});
}
/**
* Post-registration UI wiring — keeps modules.ts free of bridge side-effects.
*/
var log$45 = Logger.scope("Lifecycle");
var wired = false;
/** Call after registerModules(), before AppContainer.bootstrap(). */
function wireUiBridges() {
	if (wired) {
		log$45.debug("UI bridges already wired");
		return;
	}
	wired = true;
	initSettingsPanelBridge();
	initToastBridge();
	BatchBar.init();
	log$45.info("UI bridges wired");
}
var log$44 = Logger.scope("Messaging");
var MessageType = /* @__PURE__ */ function(MessageType) {
	MessageType["SETTING_UPDATE"] = "SETTING_UPDATE";
	MessageType["HISTORY_UPDATE"] = "HISTORY_UPDATE";
	MessageType["UI_REFRESH"] = "UI_REFRESH";
	return MessageType;
}({});
var MessagingService = class {
	CHANNEL_NAME = SYSTEM_KEYS.MESSAGING_CHANNEL;
	tabId = Math.random().toString(36).substring(2, 11);
	channel = null;
	listeners = /* @__PURE__ */ new Set();
	createChannel() {
		return new BroadcastChannel(this.CHANNEL_NAME);
	}
	onInit() {
		this.onCleanup();
		this.channel = this.createChannel();
		this.channel.onmessage = (event) => {
			const msg = event.data;
			if (msg.sourceTabId === this.tabId) return;
			log$44.debug(`Received ${msg.type} from other tab`);
			this.listeners.forEach((l) => l(msg));
		};
	}
	broadcast(type, payload) {
		if (!this.channel) this.channel = this.createChannel();
		const msg = {
			type,
			payload,
			sourceTabId: this.tabId
		};
		this.channel.postMessage(msg);
		log$44.debug(`Broadcasted ${type}`);
	}
	onMessage(handler) {
		this.listeners.add(handler);
		return () => this.listeners.delete(handler);
	}
	destroy() {
		this.listeners.clear();
		this.channel?.close();
		this.channel = null;
	}
	onCleanup() {
		this.destroy();
	}
};
/** Constructed once from app/modules (composition root). */
function createMessagingService() {
	return new MessagingService();
}
var STATE_SERVICE_SENSITIVE_PROPS = [
	"supabaseKey",
	"supabasePassword",
	"webdavPass"
];
var STATE_RUNTIME_ONLY_PROPS = [
	"syncStatus",
	"isSelectionMode",
	"lastSyncTs"
];
var STATE_SERVICE_SENSITIVE_MAP = {
	supabaseKey: "SUPABASE_KEY",
	supabasePassword: "SUPABASE_PASSWORD",
	webdavPass: "WEBDAV_PASS"
};
var createDefaultAppState = () => {
	return {
		previewMode: "static",
		hideNoMagnet: false,
		hideCensored: false,
		enableHistory: true,
		hideViewed: false,
		supjavSortByViews: false,
		loadExtraPreviews: false,
		enableQuickBar: true,
		showViewedBtn: true,
		showIdBadge: true,
		enableMagnets: true,
		enableExternalLinks: true,
		language: normalizeLocalePreference(Storage.get(STORAGE_KEYS.LANGUAGE, "auto") || "auto"),
		lastSyncTs: UI_CONSTANTS.DEFAULT_TIMESTAMP,
		supabaseUrl: Storage.get(STORAGE_KEYS.SUPABASE_URL, "") || "",
		supabaseKey: Storage.get(STORAGE_KEYS.SUPABASE_KEY, "") || "",
		supabaseEmail: Storage.get(STORAGE_KEYS.SUPABASE_EMAIL, "") || "",
		supabasePassword: Storage.get(STORAGE_KEYS.SUPABASE_PASSWORD, "") || "",
		webdavUrl: Storage.get(STORAGE_KEYS.WEBDAV_URL, "") || "",
		webdavUser: Storage.get(STORAGE_KEYS.WEBDAV_USER, "") || "",
		webdavPass: Storage.get(STORAGE_KEYS.WEBDAV_PASS, "") || "",
		webdavPath: Storage.get(STORAGE_KEYS.WEBDAV_PATH, UI_CONSTANTS.DEFAULT_SYNC_FILENAME) || UI_CONSTANTS.DEFAULT_SYNC_FILENAME,
		syncMode: "none",
		syncStatus: "idle",
		syncInterval: 5,
		enabledPortals: [
			"supjav",
			"missav",
			"javdb",
			"javbus",
			"javlibrary",
			"dmm",
			"fc2",
			"fd2ppv",
			"fc2db",
			"sukebei",
			"fc2cmadb"
		],
		userGridColumns: Number(Storage.get(STORAGE_KEYS.USER_GRID_COLUMNS, 0)) || 0,
		debugMode: Storage.get(STORAGE_KEYS.DEBUG_MODE, false) || false,
		cardDensity: Storage.get(STORAGE_KEYS.CARD_DENSITY, "standard") || "standard",
		cardPrimaryActions: [
			"id",
			"viewed",
			"play",
			"preview",
			"magnet",
			"external"
		],
		lastSyncResult: Storage.get(STORAGE_KEYS.LAST_SYNC_RESULT, null) || null,
		settingsCollapsedGroups: Storage.get(STORAGE_KEYS.SETTINGS_COLLAPSED_GROUPS, []) || [],
		settingsPreset: Storage.get(STORAGE_KEYS.SETTINGS_PRESET, "custom") || "custom",
		accentColor: Storage.get(STORAGE_KEYS.ACCENT_COLOR, "#ffffff") || "#ffffff",
		isSelectionMode: false
	};
};
/** Current state schema version — bump when adding/removing/rename fields */
var STATE_SCHEMA_VERSION = 3;
var migrateState = (stored, version) => {
	if (version < 2) {
		delete stored["enableFollows"];
		delete stored["supabaseKey"];
		delete stored["supabasePassword"];
		delete stored["webdavPass"];
	}
	if (version < 3) {
		if (stored["cardDensity"] === "minimal") stored["cardDensity"] = "compact";
		if (stored["cardDensity"] === "balanced") stored["cardDensity"] = "standard";
	}
	return stored;
};
var createStoredAppState = () => {
	const defaults = createDefaultAppState();
	const stored = Storage.get(Config.STORAGE_KEYS.SETTINGS, {}) || {};
	const storedVersion = stored._version ?? 1;
	if (storedVersion < STATE_SCHEMA_VERSION) migrateState(stored, storedVersion);
	if (stored.syncMode === void 0) {
		const value = Storage.get(STORAGE_KEYS.SYNC_MODE, "none");
		stored.syncMode = [
			"none",
			"supabase",
			"webdav"
		].includes(value) ? value : "none";
	}
	for (const prop of STATE_SERVICE_SENSITIVE_PROPS) delete stored[prop];
	for (const prop of STATE_RUNTIME_ONLY_PROPS) delete stored[prop];
	const knownKeys = new Set(Object.keys(defaults));
	for (const key of Object.keys(stored)) if (key !== "_version" && !knownKeys.has(key)) delete stored[key];
	const merged = {
		...defaults,
		...stored,
		_version: STATE_SCHEMA_VERSION
	};
	merged.language = normalizeLocalePreference(String(merged.language || "auto"));
	return merged;
};
var createPersistState = (log) => {
	return Utils.debounce((data) => {
		try {
			const appState = data;
			const { syncStatus: _syncStatus, lastSyncTs: _lastSyncTs, supabaseKey: _supabaseKey, supabasePassword: _supabasePassword, webdavPass: _webdavPass, isSelectionMode: _isSelectionMode, ...toSave } = appState;
			Storage.set(Config.STORAGE_KEYS.SETTINGS, toSave);
			for (const prop of STATE_SERVICE_SENSITIVE_PROPS) {
				const value = appState[prop];
				if (typeof value !== "string" || !value) continue;
				const storageKey = STATE_SERVICE_SENSITIVE_MAP[prop];
				if (storageKey) {
					log.debug(`Saving sensitive prop: ${prop} (len: ${value.length})`);
					Storage.set(STORAGE_KEYS[storageKey], value);
				}
			}
		} catch (error) {
			log.error("Failed to save state", error);
		}
	}, TIMING.DEBOUNCE_MS);
};
var createStateProxy = ({ rawState, persistState, shouldBroadcast, broadcastUpdate, emitStateChange, emitLanguageChange }) => {
	return new Proxy(rawState, {
		get: (target, prop) => {
			return Reflect.get(target, prop);
		},
		set: (target, prop, value) => {
			if (typeof prop !== "string" || !(prop in target)) {
				Reflect.set(target, prop, value);
				return true;
			}
			const key = prop;
			if (key === "language" && typeof value === "string") value = normalizeLocalePreference(value);
			const currentValue = target[key];
			if (currentValue === value) return true;
			if (typeof currentValue === "object" && currentValue !== null && typeof value === "object" && value !== null) {
				if (Array.isArray(currentValue) && Array.isArray(value)) {
					if (currentValue.length === value.length && currentValue.every((v, i) => v === value[i])) return true;
				} else if (!Array.isArray(currentValue) && !Array.isArray(value)) {
					const curKeys = Object.keys(currentValue);
					const newKeys = Object.keys(value);
					if (curKeys.length === newKeys.length && curKeys.every((k) => currentValue[k] === value[k])) return true;
				}
			}
			target[key] = value;
			if (key === "syncStatus") {
				emitStateChange({
					prop: key,
					value
				});
				return true;
			}
			persistState(target);
			if (shouldBroadcast()) broadcastUpdate({
				prop: key,
				value
			});
			if (key === "cardPrimaryActions") CoreEvents.emit(AppEvents.CONFIG_CHANGED, {
				key,
				value
			});
			emitStateChange({
				prop: key,
				value
			});
			if (key === "language") emitLanguageChange(value);
			return true;
		}
	});
};
var hydrateSensitiveState = (proxy, log) => {
	for (const prop of STATE_SERVICE_SENSITIVE_PROPS) {
		const sensitiveKey = STATE_SERVICE_SENSITIVE_MAP[prop];
		if (!sensitiveKey) continue;
		const storageKey = STORAGE_KEYS[sensitiveKey];
		const raw = Storage.get(storageKey, "");
		log.debug(`Hydrating ${prop}: storageKey=${storageKey}, hasValue=${!!raw}, len=${raw?.length || 0}`);
		if (!raw) continue;
		proxy[prop] = raw;
	}
};
var bindCrossTabStateSync = (onRemoteUpdate, messaging) => {
	const detachMessaging = messaging.onMessage((message) => {
		if (message.type !== MessageType.SETTING_UPDATE) return;
		onRemoteUpdate(message.payload);
	});
	let detachGM = null;
	if (typeof GM_addValueChangeListener !== "undefined") {
		const listenerId = GM_addValueChangeListener(STORAGE_KEYS.SETTINGS, (_key, oldVal, newVal, remote) => {
			if (!remote) return;
			const oldObj = oldVal || {};
			const newObj = newVal || {};
			Object.keys(newObj).forEach((k) => {
				const prop = k;
				if (newObj[prop] !== oldObj[prop] && JSON.stringify(newObj[prop]) !== JSON.stringify(oldObj[prop])) onRemoteUpdate({
					prop,
					value: newObj[prop]
				});
			});
		});
		detachGM = () => GM_removeValueChangeListener(listenerId);
	}
	return () => {
		detachMessaging();
		detachGM?.();
	};
};
var log$43 = Logger.scope("State");
var StateService = class {
	deps;
	proxy;
	ready;
	skipBroadcast = false;
	resolveReady;
	detachCrossTabSync = null;
	detachDebugModeListener = null;
	constructor(deps) {
		this.deps = deps;
		this.ready = new Promise((resolve) => {
			this.resolveReady = resolve;
		});
		const persistState = createPersistState(log$43);
		this.proxy = createStateProxy({
			rawState: createStoredAppState(),
			persistState,
			shouldBroadcast: () => !this.skipBroadcast,
			broadcastUpdate: (change) => {
				this.deps.messaging.broadcast(MessageType.SETTING_UPDATE, change);
			},
			emitStateChange: (change) => {
				CoreEvents.emit(AppEvents.STATE_CHANGED, {
					prop: change.prop,
					value: change.value
				});
			},
			emitLanguageChange: (language) => {
				CoreEvents.emit(AppEvents.LANGUAGE_CHANGED, language);
			}
		});
	}
	onInit() {
		try {
			this.onCleanup();
			log$43.debug("Initializing StateService (secret decryption)");
			this.detachCrossTabSync = bindCrossTabStateSync(({ prop, value }) => {
				if (this.proxy[prop] === value) return;
				this.skipBroadcast = true;
				try {
					this.proxy[prop] = value;
				} finally {
					this.skipBroadcast = false;
				}
			}, this.deps.messaging);
			this.detachDebugModeListener = CoreEvents.on(AppEvents.STATE_CHANGED, ({ prop, value }) => {
				if (prop !== "debugMode") return;
				if (value) {
					Logger.enable(false);
					return;
				}
				Logger.disable(false);
			});
			if (this.proxy.debugMode) Logger.enable(false);
			hydrateSensitiveState(this.proxy, log$43);
		} finally {
			this.resolveReady();
			log$43.info("StateService ready");
		}
	}
	onCleanup() {
		this.detachCrossTabSync?.();
		this.detachCrossTabSync = null;
		this.detachDebugModeListener?.();
		this.detachDebugModeListener = null;
		this.skipBroadcast = false;
	}
	get(key) {
		return this.proxy[key];
	}
	set(key, value) {
		this.proxy[key] = value;
	}
	setDynamic(prop, value) {
		this.proxy[prop] = value;
	}
	toRecord() {
		return { ...this.proxy };
	}
	on(prop, listener) {
		if (typeof prop === "function") {
			const handler = prop;
			return CoreEvents.on(AppEvents.STATE_CHANGED, handler);
		}
		return CoreEvents.on(AppEvents.STATE_CHANGED, (change) => {
			if (change.prop === prop && listener) listener(change.value);
		});
	}
};
var createStateService = (deps) => new StateService(deps);
/** Settings keys allowed in backup import/export (whitelist). */
var ALLOWED_BACKUP_SETTINGS_KEYS = /* @__PURE__ */ new Set([
	"previewMode",
	"hideNoMagnet",
	"hideCensored",
	"enableHistory",
	"hideViewed",
	"loadExtraPreviews",
	"enableQuickBar",
	"showViewedBtn",
	"showIdBadge",
	"enableMagnets",
	"enableExternalLinks",
	"language",
	"syncMode",
	"syncInterval",
	"enabledPortals",
	"userGridColumns",
	"debugMode",
	"cardDensity",
	"cardPrimaryActions",
	"settingsPreset"
]);
var filterImportedSettings = (settings) => {
	const filtered = [];
	for (const [key, value] of Object.entries(settings)) if (ALLOWED_BACKUP_SETTINGS_KEYS.has(key)) filtered.push([key, value]);
	return filtered;
};
var VALID_HISTORY_STATUSES = /* @__PURE__ */ new Set(["watched", "downloaded"]);
var parseTimestamp = (value) => {
	if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
	if (typeof value === "string" && value.trim()) {
		const parsed = Date.parse(value);
		if (Number.isFinite(parsed) && parsed > 0) return parsed;
	}
	return null;
};
var normalizeBinaryFlag = (value) => value === 1 || value === true ? 1 : 0;
var getRecordTimestamp = (record) => {
	return parseTimestamp(record.updated_at) ?? parseTimestamp(record.timestamp) ?? 0;
};
var shouldReplaceImportedRecord = (incoming, existing) => {
	return getRecordTimestamp(incoming) > getRecordTimestamp(existing);
};
var normalizeHistoryItem = (value, now) => {
	if (!value || typeof value !== "object") return null;
	const candidate = value;
	const id = typeof candidate.id === "string" ? candidate.id.trim() : "";
	const status = typeof candidate.status === "string" ? candidate.status : "";
	if (!id || !VALID_HISTORY_STATUSES.has(status)) return null;
	const updatedAtTimestamp = parseTimestamp(candidate.updated_at) ?? parseTimestamp(candidate.timestamp) ?? now;
	return {
		id,
		status,
		timestamp: parseTimestamp(candidate.timestamp) ?? updatedAtTimestamp,
		updated_at: new Date(updatedAtTimestamp).toISOString(),
		is_deleted: normalizeBinaryFlag(candidate.is_deleted),
		sync_dirty: normalizeBinaryFlag(candidate.sync_dirty)
	};
};
var normalizeUniqueRecords = (items, normalizeItem, now) => {
	const dedupedItems = /* @__PURE__ */ new Map();
	items.forEach((item) => {
		const normalized = normalizeItem(item, now);
		if (!normalized) return;
		const existing = dedupedItems.get(normalized.id);
		if (!existing || shouldReplaceImportedRecord(normalized, existing)) dedupedItems.set(normalized.id, normalized);
	});
	return Array.from(dedupedItems.values());
};
var normalizeBackupPayload = (value, defaultAppName) => {
	const now = Date.now();
	return {
		appName: typeof value.appName === "string" && value.appName.trim() ? value.appName : defaultAppName,
		version: value.version,
		timestamp: parseTimestamp(value.timestamp) ?? now,
		settings: value.settings,
		history: normalizeUniqueRecords(value.history, normalizeHistoryItem, now)
	};
};
var classifyBackupPayload = (data, maxVersion = 4) => {
	if (!data || typeof data !== "object") return {
		ok: false,
		errorCode: "invalid_payload"
	};
	const candidate = data;
	const version = Number(candidate.version);
	if (!Number.isInteger(version) || version <= 0) return {
		ok: false,
		errorCode: "invalid_payload"
	};
	if (version > maxVersion) return {
		ok: false,
		errorCode: "unsupported_version"
	};
	if (!candidate.settings || typeof candidate.settings !== "object" || !Array.isArray(candidate.history)) return {
		ok: false,
		errorCode: "invalid_payload"
	};
	const payload = {
		appName: typeof candidate.appName === "string" ? candidate.appName : "",
		version,
		timestamp: parseTimestamp(candidate.timestamp) ?? Date.now(),
		settings: candidate.settings,
		history: candidate.history
	};
	return {
		ok: true,
		payload: normalizeBackupPayload(payload, payload.appName || "unknown")
	};
};
var parseBackupJson = (content, maxVersion = 4) => {
	let parsed;
	try {
		parsed = JSON.parse(content);
	} catch {
		return {
			ok: false,
			errorCode: "invalid_json"
		};
	}
	return classifyBackupPayload(parsed, maxVersion);
};
var buildBackupImportDiff = ({ payload, currentSettings, existingHistoryCount }) => {
	const settingsAdded = [];
	const settingsChanged = [];
	for (const [key, value] of filterImportedSettings(payload.settings || {})) {
		if (!(key in currentSettings)) {
			settingsAdded.push(String(key));
			continue;
		}
		const currentValue = currentSettings[key];
		if (JSON.stringify(currentValue) !== JSON.stringify(value)) settingsChanged.push(String(key));
	}
	return {
		settingsAdded,
		settingsChanged,
		historyToImport: payload.history.length,
		existingHistoryCount
	};
};
var createBackupExportPayload$1 = ({ appName, settings, history, version = 4, timestamp = Date.now() }) => ({
	appName,
	version,
	timestamp,
	settings,
	history
});
var createBackupExportPayload = ({ settings, history }) => createBackupExportPayload$1({
	appName: SCRIPT_INFO.NAME,
	settings,
	history
});
var parseBackupContentWithMetadataDetailed = (content) => {
	return parseBackupJson(content, 4);
};
var createBackupImportPreview = ({ payload, currentSettings, existingHistoryCount }) => ({
	payload,
	diff: buildBackupImportDiff({
		payload,
		currentSettings,
		existingHistoryCount
	})
});
var defaultTranslate$1 = (key, params) => {
	let value = {
		backupSettingsAdded: "Settings added: {count}",
		backupSettingsChanged: "Settings changed: {count}",
		backupHistoryRowsWithCurrent: "History rows to import: {count} (current: {current})",
		backupChangedKeys: "Changed keys: {keys}"
	}[key] || key;
	if (params) Object.entries(params).forEach(([paramKey, paramValue]) => {
		value = value.replace(new RegExp(`{${paramKey}}`, "g"), String(paramValue));
	});
	return value;
};
var formatBackupImportDiff = exports("g",(diff, translate = defaultTranslate$1) => {
	const lines = [
		translate("backupSettingsAdded", { count: diff.settingsAdded.length }),
		translate("backupSettingsChanged", { count: diff.settingsChanged.length }),
		translate("backupHistoryRowsWithCurrent", {
			count: diff.historyToImport,
			current: diff.existingHistoryCount
		})
	];
	if (diff.settingsChanged.length > 0) lines.push(translate("backupChangedKeys", { keys: diff.settingsChanged.join(", ") }));
	return lines.join("\n");
});
var log$42 = Logger.scope("Backup");
var BackupService = class {
	deps;
	constructor(deps) {
		this.deps = deps;
	}
	get data() {
		return this.deps.data;
	}
	get state() {
		return this.deps.state;
	}
	onInit() {
		log$42.debug("Service initialized");
	}
	async readFileAsText(file) {
		return await new Promise((resolve, reject) => {
			const reader = new FileReader();
			reader.onload = (event) => resolve(String(event.target?.result || ""));
			reader.onerror = () => reject(/* @__PURE__ */ new Error("Failed to read backup file"));
			reader.readAsText(file);
		});
	}
	async parseFile(file) {
		return parseBackupContentWithMetadataDetailed(await this.readFileAsText(file));
	}
	async exportData() {
		const history = await this.data.history.getAll();
		const data = createBackupExportPayload({
			settings: Object.fromEntries(filterImportedSettings(this.state.proxy)),
			history
		});
		try {
			const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
			const url = URL.createObjectURL(blob);
			const anchor = document.createElement("a");
			const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
			anchor.href = url;
			anchor.download = `fc2_enhanced_backup_${dateStr}.json`;
			anchor.click();
			setTimeout(() => {
				URL.revokeObjectURL(url);
				anchor.remove();
			}, 100);
			log$42.info("Data exported successfully");
			return true;
		} catch (error) {
			log$42.error("Export failed", error);
			return false;
		}
	}
	async previewImportDetailed(file) {
		try {
			const parsed = await this.parseFile(file);
			if (!parsed.ok) return {
				ok: false,
				errorCode: parsed.errorCode
			};
			const historyCount = await this.data.db.history.count();
			return {
				ok: true,
				preview: createBackupImportPreview({
					payload: parsed.payload,
					currentSettings: this.state.proxy,
					existingHistoryCount: historyCount
				})
			};
		} catch (error) {
			log$42.error("Preview import failed", error);
			return {
				ok: false,
				errorCode: "read_failed"
			};
		}
	}
	async previewImport(file) {
		return (await this.previewImportDetailed(file)).preview ?? null;
	}
	async importDataDetailed(file) {
		const previewResult = await this.previewImportDetailed(file);
		if (!previewResult.ok || !previewResult.preview) return {
			success: false,
			errorCode: previewResult.errorCode ?? "invalid_payload"
		};
		try {
			await this.applyImport(previewResult.preview.payload);
			log$42.info("Backup imported", { diff: formatBackupImportDiff(previewResult.preview.diff) });
			return {
				success: true,
				preview: previewResult.preview
			};
		} catch (error) {
			log$42.error("Import failed during parsing/saving", error);
			return {
				success: false,
				preview: previewResult.preview,
				errorCode: "apply_failed"
			};
		}
	}
	async importData(file) {
		return (await this.importDataDetailed(file)).success;
	}
	async applyImport(data) {
		log$42.debug("Importing settings...");
		const settings = data.settings || {};
		for (const [key, value] of filterImportedSettings(settings)) {
			const nextValue = key === "language" && typeof value === "string" ? normalizeLocalePreference(value) : value;
			this.state.proxy[key] = nextValue;
		}
		log$42.debug("Importing history...");
		await this.data.db.transaction("rw", [this.data.db.history], async () => {
			if (Array.isArray(data.history)) await this.data.db.history.bulkPut(data.history.map((item) => ({
				...item,
				sync_dirty: 1
			})));
		});
	}
};
/** Constructed once from app/modules (composition root). */
function createBackupService(deps) {
	return new BackupService(deps);
}
var ENH_GRID_CONTAINER_ATTR = "data-enh-grid-container";
var ENH_GRID_CONTAINER_SELECTOR = `[${ENH_GRID_CONTAINER_ATTR}]`;
var markGridContainer = (container) => {
	if (!container) return;
	container.setAttribute(ENH_GRID_CONTAINER_ATTR, "1");
};
var SITE_GRID_MAP = [
	["fd2ppv.cc", {
		cont: ENH_GRID_CONTAINER_SELECTOR,
		card: `> .${Config.CLASSES.cardRebuilt}`
	}],
	["supjav.com", {
		cont: ENH_GRID_CONTAINER_SELECTOR,
		card: `> .${Config.CLASSES.cardRebuilt}`
	}],
	["missav", {
		cont: ENH_GRID_CONTAINER_SELECTOR,
		card: `> .${Config.CLASSES.cardRebuilt}`
	}],
	["javdb", {
		cont: ENH_GRID_CONTAINER_SELECTOR,
		card: `> .${Config.CLASSES.cardRebuilt}`
	}],
	["fc2db", {
		cont: ENH_GRID_CONTAINER_SELECTOR,
		card: `> .${Config.CLASSES.cardRebuilt}`
	}],
	["javfc2.xyz", {
		cont: `${ENH_GRID_CONTAINER_SELECTOR}:not(.owl-stage)`,
		card: `> *`
	}],
	["fc2cmadb.com", {
		cont: ENH_GRID_CONTAINER_SELECTOR,
		card: `> .${Config.CLASSES.cardRebuilt}`
	}]
];
var shouldSkipGridApply = (hostname, pathname) => {
	if (!hostname.includes("missav")) return false;
	return /\/(cn\/|en\/|ja\/)?(fc2-ppv-|[a-z]{2,5}-)\d+/i.test(pathname);
};
var resolveGridSelectors = (hostname) => {
	return SITE_GRID_MAP.find(([match]) => hostname.includes(match))?.[1] ?? null;
};
var buildGridCss = (hostname, pathname, cols) => {
	if (shouldSkipGridApply(hostname, pathname)) return "";
	const selectors = resolveGridSelectors(hostname);
	if (!selectors || cols <= 0) return "";
	const cardRules = selectors.cont.split(",").map((selector) => selector.trim()).map((selector) => `${selector} ${selectors.card}`).join(", ");
	const baseGridCss = `
            display: grid !important;
            grid-template-columns: repeat(${Math.min(2, cols)}, minmax(0, 1fr)) !important;
            gap: var(--fc2-space-md) !important;
            margin: 0 !important;
            padding: var(--fc2-space-md) 10px !important;
            width: 100% !important;
            max-width: none !important;
            box-sizing: border-box !important;
        `;
	const cardCss = selectors.card ? `${cardRules} { padding: 0 !important; margin: 0 !important; width: 100% !important; max-width: none !important; flex: none !important; box-sizing: border-box !important; }` : "";
	let siteSpecificCss = "";
	if (hostname.includes("javfc2.xyz")) {
		const C = Config.CLASSES;
		siteSpecificCss = `
            ${selectors.cont} > [class*="col-"] {
                width: 100% !important;
                max-width: none !important;
                flex: none !important;
                padding: 0 !important;
                display: flex !important;
            }
            .${C.cardRebuilt} {
                width: 100% !important;
                max-width: none !important;
                height: 100% !important;
                display: flex !important;
            }
            .${C.processedCard} {
                width: 100% !important;
                height: 100% !important;
                flex: 1 1 auto !important;
            }
            .${C.videoPreviewContainer} {
                aspect-ratio: 3 / 4 !important;
                background-color: var(--fc2-bg) !important;
                flex: 0 0 auto !important;
            }
            .${C.infoArea} {
                flex: 1 1 auto !important;
                min-height: calc(var(--fc2-btn-lg) + var(--fc2-space-xl) + var(--fc2-space-xs)) !important;
                padding: var(--fc2-space-sm) !important;
            }
        `;
	}
	const tabletCols = Math.min(cols, 4);
	return `
            ${siteSpecificCss}
            ${selectors.cont} { ${baseGridCss} }
            ${cardCss}
            ${selectors.cont} .inner { padding: 0 !important; }

            @media (max-width: 480px) {
                ${selectors.cont} {
                    grid-template-columns: minmax(0, 1fr) !important;
                    gap: var(--fc2-space-sm) !important;
                }
            }

            @media (min-width: 768px) {
                ${selectors.cont} {
                    grid-template-columns: repeat(${tabletCols}, minmax(0, 1fr)) !important;
                }
            }

            @media (min-width: 1200px) {
                ${selectors.cont} {
                    grid-template-columns: repeat(${cols}, minmax(0, 1fr)) !important;
                }
            }
        `;
};
var GridService = class {
	styleEl = null;
	cleanupFns = [];
	onBootstrap() {
		this.onCleanup();
		this.cleanupFns.push(CoreEvents.on(AppEvents.GRID_CHANGED, (cols) => this.apply(cols)));
		this.cleanupFns.push(CoreEvents.on(AppEvents.STATE_CHANGED, ({ prop, value }) => {
			if (prop === "userGridColumns") this.apply(Number(value));
		}));
		const cols = resolveService(STATE_SERVICE).proxy.userGridColumns;
		if (cols > 0) this.apply(cols);
	}
	onCleanup() {
		this.cleanupFns.splice(0).forEach((cleanup) => cleanup());
		this.styleEl?.remove();
		this.styleEl = null;
	}
	apply(cols) {
		if (!document.head) return;
		if (!this.styleEl) {
			this.styleEl = document.createElement("style");
			this.styleEl.id = "fc2-modern-grid-style";
			document.head.appendChild(this.styleEl);
		}
		this.styleEl.textContent = buildGridCss(location.hostname, location.pathname, cols);
	}
};
/** Constructed once from app/modules (composition root). */
function createGridManager() {
	return new GridService();
}
/** Canonical media ID normalization (FC2 numeric + JAV codes). */
var IdNormalizer = {
	normalize(raw) {
		if (!raw) return "";
		const trimmed = raw.trim().toUpperCase();
		const fc2Match = trimmed.match(/^(?:FC2|PPV)?-?(\d{5,8})$/);
		if (fc2Match && fc2Match[1]) return fc2Match[1].replace(/^0+/, "") || "";
		if (trimmed.match(VALIDATION.JAV_ID_REGEX)) return trimmed;
		return trimmed;
	},
	isSame(id1, id2) {
		const n1 = this.normalize(id1);
		const n2 = this.normalize(id2);
		if (!n1 || !n2) return false;
		if (n1 === n2) return true;
		if (n1.replace(/-/g, "") === n2.replace(/-/g, "")) return true;
		return false;
	}
};
var touchHistoryCacheEntry = (cache, key, value, maxSize) => {
	if (cache.has(key)) cache.delete(key);
	cache.set(key, value);
	if (cache.size > maxSize) {
		const firstKey = cache.keys().next().value;
		if (firstKey) cache.delete(firstKey);
	}
};
var hydrateHistoryCache = (cache, items, normalizeId) => {
	cache.clear();
	for (let index = items.length - 1; index >= 0; index--) {
		const item = items[index];
		if (item && !item.is_deleted) cache.set(normalizeId(item.id), item.status || "watched");
	}
};
var hasHistoryEntry = (enabled, cache, id, status) => {
	if (!enabled) return false;
	if (status) return cache.get(id) === status;
	return cache.has(id);
};
var log$41 = Logger.scope("History");
var HistoryService = class {
	deps;
	CACHE_SIZE = 5e3;
	historyCache = /* @__PURE__ */ new Map();
	detachMessageListener = null;
	detachTimelineListeners = [];
	lastTimelineGcTime = 0;
	loadReadyPromise = Promise.resolve();
	constructor(deps) {
		this.deps = deps;
	}
	get data() {
		return this.deps.data;
	}
	get state() {
		return this.deps.state;
	}
	get messaging() {
		return this.deps.messaging;
	}
	get historyEnabled() {
		return !!this.state.get("enableHistory");
	}
	getHistorySnapshot() {
		return this.historyCache;
	}
	onBootstrap() {
		this.onCleanup();
		log$41.debug("Warming up cache");
		this.loadReadyPromise = this.load().catch((error) => log$41.error("Failed to load history cache", error));
		this.detachMessageListener = this.messaging.onMessage((message) => {
			if (message.type !== MessageType.HISTORY_UPDATE) return;
			const { action, id, status } = message.payload;
			if (action === "add" && status) {
				this.add(id, status, true);
				return;
			}
			if (action === "remove") {
				this.remove(id, true);
				return;
			}
			if (action === "add-batch" && message.payload.ids && status) {
				this.addBatch(message.payload.ids, status, true);
				return;
			}
			if (action === "remove-batch" && message.payload.ids) {
				this.removeBatch(message.payload.ids, true);
				return;
			}
			if (action === "clear") this.clear(true);
		});
		this.detachTimelineListeners.push(CoreEvents.on(AppEvents.CARD_DISCOVERED, (payload) => {
			this.record("card", "discovered", payload.id).catch((e) => log$41.debug("timeline record failed", e));
		}));
		this.detachTimelineListeners.push(CoreEvents.on(AppEvents.MAGNET_FOUND, (payload) => {
			this.record("magnet", "found", payload.id).catch((e) => log$41.debug("timeline record failed", e));
		}));
		this.detachTimelineListeners.push(CoreEvents.on(AppEvents.HISTORY_CHANGED, () => {
			this.record("history", "changed").catch((e) => log$41.debug("timeline record failed", e));
		}));
		this.detachTimelineListeners.push(CoreEvents.on(AppEvents.SYNC_STATUS_CHANGED, (status) => {
			if (status === "success") this.record("sync", "completed").catch((e) => log$41.debug("timeline record failed", e));
		}));
	}
	onCleanup() {
		this.detachMessageListener?.();
		this.detachMessageListener = null;
		this.detachTimelineListeners.forEach((fn) => fn());
		this.detachTimelineListeners = [];
	}
	getCache() {
		return new Map(this.historyCache);
	}
	async waitUntilReady() {
		await this.loadReadyPromise;
	}
	async load() {
		Logger.time("HistoryService.load");
		if (!this.historyEnabled) {
			this.historyCache.clear();
			Logger.timeEnd("HistoryService.load");
			return;
		}
		try {
			let items = [];
			try {
				items = await this.data.history.table.orderBy("timestamp").reverse().limit(this.CACHE_SIZE).toArray();
			} catch (indexError) {
				log$41.warn("Index scan failed, falling back to full scan", indexError);
				items = await this.data.history.table.toArray();
				items.sort((left, right) => right.timestamp - left.timestamp);
				if (items.length > this.CACHE_SIZE) items = items.slice(0, this.CACHE_SIZE);
			}
			hydrateHistoryCache(this.historyCache, items, (id) => this.getNormalizedId(id));
			CoreEvents.emit(AppEvents.HISTORY_LOADED, items.length);
			log$41.info(`Loaded ${items.length} recent items`);
		} catch (error) {
			log$41.error("Failed to load history", error);
			this.historyCache.clear();
		}
		Logger.timeEnd("HistoryService.load");
	}
	async add(id, status = "watched", remote = false) {
		if (!this.historyEnabled || !id) return;
		const normalizedId = this.getNormalizedId(String(id));
		if (this.historyCache.get(normalizedId) === status) {
			this.touch(normalizedId, status);
			if (!remote) return;
		}
		const previousStatus = this.historyCache.get(normalizedId);
		this.touch(normalizedId, status);
		if (!remote) {
			try {
				await this.data.history.add(normalizedId, status);
			} catch (error) {
				log$41.error(`Failed to persist history add for ${normalizedId}`, error);
				if (previousStatus) this.touch(normalizedId, previousStatus);
				else this.historyCache.delete(normalizedId);
				return;
			}
			this.messaging.broadcast(MessageType.HISTORY_UPDATE, {
				action: "add",
				id: normalizedId,
				status
			});
		}
		CoreEvents.emit(AppEvents.HISTORY_ADDED, {
			id: normalizedId,
			status
		});
		log$41.debug(`${remote ? "Remote" : "Local"} added: ${normalizedId} as ${status}`);
		if (!remote) CoreEvents.emit(AppEvents.HISTORY_CHANGED, {});
	}
	async remove(id, remote = false) {
		if (!this.historyEnabled || !id) return;
		const normalizedId = this.getNormalizedId(String(id));
		const previousStatus = this.historyCache.get(normalizedId);
		this.historyCache.delete(normalizedId);
		if (!remote) {
			try {
				await this.data.history.remove(normalizedId);
			} catch (error) {
				log$41.error(`Failed to persist history remove for ${normalizedId}`, error);
				if (previousStatus) this.touch(normalizedId, previousStatus);
				return;
			}
			this.messaging.broadcast(MessageType.HISTORY_UPDATE, {
				action: "remove",
				id: normalizedId
			});
		}
		CoreEvents.emit(AppEvents.HISTORY_REMOVED, {
			id: normalizedId,
			prevStatus: previousStatus
		});
		log$41.debug(`${remote ? "Remote" : "Local"} removed: ${normalizedId}`);
		if (!remote) CoreEvents.emit(AppEvents.HISTORY_CHANGED, {});
	}
	async addBatch(ids, status = "watched", remote = false) {
		if (!this.historyEnabled || ids.length === 0) return;
		const normalizedIds = ids.map((id) => this.getNormalizedId(id));
		const previousStatuses = /* @__PURE__ */ new Map();
		normalizedIds.forEach((id) => {
			previousStatuses.set(id, this.historyCache.get(id));
			this.touch(id, status);
		});
		if (!remote) try {
			await this.data.history.bulkAdd(normalizedIds, status);
			this.messaging.broadcast(MessageType.HISTORY_UPDATE, {
				action: "add-batch",
				ids: normalizedIds,
				status
			});
		} catch (error) {
			log$41.error(`Failed to persist history bulk add`, error);
			previousStatuses.forEach((prev, id) => {
				if (prev) this.touch(id, prev);
				else this.historyCache.delete(id);
			});
			return;
		}
		normalizedIds.forEach((id) => {
			CoreEvents.emit(AppEvents.HISTORY_ADDED, {
				id,
				status
			});
		});
		if (!remote) CoreEvents.emit(AppEvents.HISTORY_CHANGED, {});
	}
	async removeBatch(ids, remote = false) {
		if (!this.historyEnabled || ids.length === 0) return;
		const normalizedIds = ids.map((id) => this.getNormalizedId(id));
		const prevStates = /* @__PURE__ */ new Map();
		normalizedIds.forEach((id) => {
			prevStates.set(id, this.historyCache.get(id));
			this.historyCache.delete(id);
		});
		if (!remote) try {
			await this.data.history.bulkRemove(normalizedIds);
			this.messaging.broadcast(MessageType.HISTORY_UPDATE, {
				action: "remove-batch",
				ids: normalizedIds
			});
		} catch (error) {
			log$41.error(`Failed to persist history bulk remove`, error);
			prevStates.forEach((status, id) => {
				if (status) this.touch(id, status);
			});
			return;
		}
		normalizedIds.forEach((id) => {
			CoreEvents.emit(AppEvents.HISTORY_REMOVED, {
				id,
				prevStatus: prevStates.get(id)
			});
		});
		if (!remote) CoreEvents.emit(AppEvents.HISTORY_CHANGED, {});
	}
	async clear(remote = false) {
		const count = this.historyCache.size;
		const backup = new Map(this.historyCache);
		this.historyCache.clear();
		if (!remote) {
			try {
				await this.data.history.clear();
			} catch (error) {
				log$41.error("Failed to persist history clear", error);
				backup.forEach((value, key) => this.historyCache.set(key, value));
				return;
			}
			this.messaging.broadcast(MessageType.HISTORY_UPDATE, { action: "clear" });
		}
		CoreEvents.emit(AppEvents.HISTORY_CLEARED, count);
		log$41.warn(`${remote ? "Remote" : "Local"} cleared ${count} items`);
		if (!remote) CoreEvents.emit(AppEvents.HISTORY_CHANGED, {});
	}
	async record(category, action, detail) {
		try {
			await this.data.timeline.add({
				timestamp: Date.now(),
				category,
				action,
				detail
			});
			await this.gcTimeline();
		} catch (e) {
			log$41.warn("Failed to record timeline event", e);
		}
	}
	async getEvents(options) {
		try {
			return await this.data.timeline.list({
				category: options?.category,
				limit: options?.limit ?? 100
			});
		} catch {
			return [];
		}
	}
	async gcTimeline() {
		const now = Date.now();
		if (now - this.lastTimelineGcTime < TIMELINE.GC_INTERVAL_MS) return;
		this.lastTimelineGcTime = now;
		try {
			const threshold = now - TIMELINE.RETENTION_MS;
			await this.data.timeline.purgeBefore(threshold);
		} catch (e) {
			log$41.debug("Timeline GC failed", e);
		}
	}
	has(id, status) {
		return hasHistoryEntry(this.historyEnabled, this.historyCache, this.getNormalizedId(String(id)), status);
	}
	getStatus(id) {
		return this.historyCache.get(this.getNormalizedId(String(id))) || null;
	}
	touch(key, value) {
		touchHistoryCacheEntry(this.historyCache, key, value, this.CACHE_SIZE);
	}
	getNormalizedId(id) {
		return IdNormalizer.normalize(id);
	}
};
/** Constructed once from app/modules (composition root). */
function createHistoryService(deps) {
	return new HistoryService(deps);
}
var createNegativeMagnetCacheValue = () => ({
	kind: "no-magnet",
	expiresAt: Date.now() + CACHE.NEGATIVE_MAGNET_EXPIRATION_MS,
	retryable: true
});
var isNegativeMagnetCacheValue = (value) => {
	return value === "@@NO_MAGNET@@" || !!value && typeof value === "object" && value.kind === "no-magnet";
};
var isExpiredNegativeMagnetCacheValue = (value) => {
	if (value === "@@NO_MAGNET@@") return false;
	if (!isNegativeMagnetCacheValue(value)) return false;
	return typeof value.expiresAt === "number" && value.expiresAt <= Date.now();
};
var log$40 = Logger.scope("Magnet");
var MagnetService = class {
	deps;
	constructor(deps) {
		this.deps = deps;
	}
	get data() {
		return this.deps.data;
	}
	get scraper() {
		return this.deps.scraper;
	}
	queue = /* @__PURE__ */ new Map();
	activeSearches = /* @__PURE__ */ new Set();
	taskTimeouts = /* @__PURE__ */ new Map();
	maxConcurrency = MAGNET_CONFIG.MAX_CONCURRENCY;
	onProgress = null;
	flushTimer = null;
	detachUiReady = null;
	onBootstrap() {
		log$40.debug("Subscribed to UI_READY");
		this.detachUiReady?.();
		this.detachUiReady = CoreEvents.on(AppEvents.UI_READY, () => {
			log$40.info("Manager active");
		});
	}
	onCleanup() {
		this.detachUiReady?.();
		this.detachUiReady = null;
		if (this.flushTimer) {
			clearTimeout(this.flushTimer);
			this.flushTimer = null;
		}
		this.taskTimeouts.forEach((timer) => clearTimeout(timer));
		this.taskTimeouts.clear();
		this.queue.forEach((task) => {
			if (task.status !== "found" && task.status !== "failed") {
				task.status = "failed";
				task.resolve(null);
			}
		});
		this.queue.clear();
		this.activeSearches.clear();
		this._updateStatus();
	}
	async fetchMagnet(id, type, options = {}) {
		if (options.forceRetry) await this.data.cache.delete(id);
		const cached = await this.data.cache.get(id);
		if (cached) {
			if (isNegativeMagnetCacheValue(cached)) {
				this._notifyUI(id, "failed");
				return null;
			}
			this._notifyUI(id, "found", cached);
			return cached;
		}
		const existingTask = this.queue.get(id);
		if (existingTask) return new Promise((resolve) => {
			const originalResolve = existingTask.resolve;
			existingTask.resolve = (url) => {
				originalResolve(url);
				resolve(url);
			};
		});
		return new Promise((resolve) => {
			const task = {
				id,
				type: type || MAGNET_CONFIG.DEFAULT_TYPE,
				resolve,
				retryCount: 0,
				status: "pending",
				startTime: Date.now()
			};
			this.queue.set(id, task);
			const timeoutHandle = setTimeout(() => {
				this.taskTimeouts.delete(id);
				const currentTask = this.queue.get(id);
				if (currentTask === task && currentTask.status !== "found" && currentTask.status !== "failed") {
					log$40.warn(`Search timed out for ${task.id}`);
					currentTask.status = "failed";
					this._notifyUI(task.id, "failed");
					currentTask.resolve(null);
					this.queue.delete(id);
				}
			}, MAGNET_CONFIG.SEARCH_TIMEOUT_MS);
			this.taskTimeouts.set(id, timeoutHandle);
			this._requestProcess();
		});
	}
	async retryFailedMagnet(id, type) {
		return await this.fetchMagnet(id, type, { forceRetry: true });
	}
	predictiveSearch(card) {
		const { id, type } = card.dataset;
		if (id && type && !this.queue.has(id) && this.queue.size < MAGNET_CONFIG.PREDICTIVE_LIMIT) this.fetchMagnet(id, type);
	}
	_requestProcess() {
		if (this.flushTimer) clearTimeout(this.flushTimer);
		this.flushTimer = setTimeout(() => {
			this._processQueue();
			this.flushTimer = null;
		}, 100);
	}
	async _processQueue() {
		if (this.activeSearches.size >= this.maxConcurrency) return;
		const batch = Array.from(this.queue.values()).filter((task) => task.status === "pending").sort((left, right) => left.startTime - right.startTime).slice(0, this.maxConcurrency - this.activeSearches.size);
		if (batch.length === 0) return;
		batch.forEach((task) => {
			task.status = "searching";
			this.activeSearches.add(task.id);
		});
		this._updateStatus();
		try {
			await this.scraper.fetchMagnets(batch.map((task) => ({
				id: task.id,
				type: task.type
			})), (id, url) => {
				const task = this.queue.get(id);
				if (!task) return;
				if (url) this._onTaskSuccess(task, url);
				else this._onTaskFailed(task, true);
			});
		} catch (error) {
			log$40.error("Batch search error", error);
			batch.forEach((task) => {
				if (task.status === "searching") this._onTaskFailed(task, true);
			});
		} finally {
			batch.forEach((task) => this.activeSearches.delete(task.id));
			this._requestProcess();
		}
	}
	async _onTaskSuccess(task, url) {
		this._clearTaskTimeout(task.id);
		await this.data.cache.set(task.id, url);
		task.status = "found";
		task.resolve(url);
		this.queue.delete(task.id);
		this._updateStatus();
		this._notifyUI(task.id, "found", url);
	}
	async _onTaskFailed(task, forceFail = false) {
		const maxRetries = MAGNET_CONFIG.MAX_RETRIES;
		if (!forceFail && task.retryCount < maxRetries) {
			task.retryCount++;
			task.status = "pending";
			task.startTime = Date.now() + Math.pow(2, task.retryCount) * MAGNET_CONFIG.RETRY_DELAY;
			log$40.debug(`Retrying ${task.id} (${task.retryCount}/${maxRetries})`);
			this._updateStatus();
			return;
		}
		this._clearTaskTimeout(task.id);
		task.status = "failed";
		task.resolve(null);
		this.queue.delete(task.id);
		this._notifyUI(task.id, "failed");
		await this.data.cache.set(task.id, createNegativeMagnetCacheValue());
		this._updateStatus();
	}
	_notifyUI(id, status, url) {
		if (status === "found" && url) CoreEvents.emit(AppEvents.MAGNET_FOUND, {
			id,
			url
		});
		else if (status === "failed") CoreEvents.emit(AppEvents.MAGNET_FAILED, { id });
	}
	_updateStatus() {
		if (!this.onProgress) return;
		this.onProgress({
			total: this.queue.size,
			active: this.activeSearches.size,
			found: Array.from(this.queue.values()).filter((t) => t.status === "found").length,
			failed: Array.from(this.queue.values()).filter((t) => t.status === "failed").length
		});
	}
	_clearTaskTimeout(id) {
		const timer = this.taskTimeouts.get(id);
		if (timer) {
			clearTimeout(timer);
			this.taskTimeouts.delete(id);
		}
	}
};
/** Constructed once from app/modules (composition root). */
function createMagnetService(deps) {
	return new MagnetService(deps);
}
var log$39 = Logger.scope("Menu");
var MenuService = class {
	menuIds = [];
	onBootstrap() {
		this.onCleanup();
		log$39.debug("Registering UserScript menus");
		this.register();
	}
	onCleanup() {
		if (typeof GM_unregisterMenuCommand === "undefined") {
			this.menuIds = [];
			return;
		}
		this.menuIds.forEach((id) => {
			try {
				GM_unregisterMenuCommand(id);
			} catch {}
		});
		this.menuIds = [];
	}
	register() {
		this.onCleanup();
		if (typeof GM_registerMenuCommand === "undefined") return;
		const settingsId = GM_registerMenuCommand(t("menuOpenSettings"), () => {
			CoreEvents.emit(AppEvents.OPEN_SETTINGS, {});
		}, "s");
		this.menuIds.push(settingsId);
	}
};
/** Constructed once from app/modules (composition root). */
function createMenuService() {
	return new MenuService();
}
var PORTAL_CATALOG = [
	{
		id: "dmm",
		name: "DMM",
		urlTemplate: EXTERNAL_URLS.DMM,
		shouldShow: ({ hostname }) => !hostname.includes("dmm.co.jp")
	},
	{
		id: "fc2",
		name: "FC2",
		urlTemplate: EXTERNAL_URLS.FC2,
		shouldShow: ({ type, hostname }) => type === "fc2" && !hostname.includes("fc2.com")
	},
	{
		id: "supjav",
		name: "Supjav",
		urlTemplate: EXTERNAL_URLS.SUPJAV,
		shouldShow: ({ hostname }) => !hostname.includes("supjav")
	},
	{
		id: "missav",
		name: "MissAV",
		urlTemplate: "",
		shouldShow: ({ hostname }) => !hostname.includes("missav")
	},
	{
		id: "javdb",
		name: "JavDB",
		urlTemplate: EXTERNAL_URLS.JAVDB,
		shouldShow: ({ hostname }) => !hostname.includes("javdb")
	},
	{
		id: "javbus",
		name: "JavBus",
		urlTemplate: EXTERNAL_URLS.JAVBUS,
		shouldShow: ({ hostname }) => !hostname.includes("javbus")
	},
	{
		id: "javlibrary",
		name: "JavLibrary",
		urlTemplate: EXTERNAL_URLS.JAVLIBRARY,
		shouldShow: ({ hostname }) => !hostname.includes("javlibrary")
	},
	{
		id: "fd2ppv",
		name: "FD2PPV",
		urlTemplate: EXTERNAL_URLS.FD2PPV,
		shouldShow: ({ type, hostname }) => type === "fc2" && !hostname.includes("fd2ppv")
	},
	{
		id: "fc2db",
		name: "FC2DB",
		urlTemplate: EXTERNAL_URLS.FC2DB,
		shouldShow: ({ type, hostname }) => type === "fc2" && !hostname.includes("fc2db")
	},
	{
		id: "sukebei",
		name: "Sukebei",
		urlTemplate: EXTERNAL_URLS.SUKEBEI,
		shouldShow: () => true
	},
	{
		id: "fc2cmadb",
		name: "FC2CMADB",
		urlTemplate: EXTERNAL_URLS.FC2CMADB,
		shouldShow: ({ type, hostname }) => type === "fc2" && !hostname.includes("fc2cmadb")
	}
];
var resolvePortalUrlTemplate = (portal, type) => {
	if (portal.id !== "missav") return portal.urlTemplate;
	return type === "fc2" ? EXTERNAL_URLS.MISSAV_FC2 : EXTERNAL_URLS.MISSAV;
};
var createAvailablePortals = ({ portals, enabledPortals, hostname, id, type }) => {
	return portals.filter((portal) => enabledPortals.includes(portal.id) && portal.shouldShow({
		id,
		type,
		hostname
	})).map((portal) => ({
		id: portal.id,
		name: portal.name,
		url: resolvePortalUrlTemplate(portal, type).replace("{id}", () => {
			const dateParts = MediaUtils.parseDateIdParts(id);
			if (portal.id === "sukebei" && dateParts) {
				const sep = dateParts.brand === "CARIBBEANCOM" ? "-" : "_";
				return `${dateParts.date}${sep}${dateParts.serial}-${dateParts.short}`;
			}
			if (portal.id === "missav") {
				let missavId = id.toLowerCase();
				if (missavId.startsWith("1pondo-") || missavId.startsWith("1pon-")) missavId = missavId.replace(/^(1pondo-|1pon-)/, "pondo-");
				if (missavId.startsWith("10musume-") || missavId.startsWith("10mu-")) missavId = missavId.replace(/^(10musume-|10mu-)/, "musume-");
				if (/^(pondo|pacopacomama|musume|caribbeancom)-/.test(missavId)) missavId = missavId.replace(/-(\d{2,4})$/, "_$1");
				return missavId;
			}
			return id;
		})
	}));
};
var getAllPortals = (portals) => {
	return portals.map((portal) => ({
		id: portal.id,
		name: portal.name
	}));
};
var getAllPortalSites = (portals) => {
	return Array.from(new Set(portals.map((portal) => portal.id)));
};
var DEFAULT_PORTAL_CACHE_MAX_SIZE = 300;
var DEFAULT_PORTAL_CACHE_TTL_MS = 6e5;
var PortalService = class {
	cache = /* @__PURE__ */ new Map();
	portals;
	cacheMaxSize;
	cacheTtlMs;
	detachEnabledPortalsListener = null;
	constructor(portals = PORTAL_CATALOG, options = {}) {
		this.portals = portals;
		this.cacheMaxSize = options.cacheMaxSize ?? DEFAULT_PORTAL_CACHE_MAX_SIZE;
		this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_PORTAL_CACHE_TTL_MS;
	}
	onInit() {
		this.onCleanup();
		this.detachEnabledPortalsListener = resolveService(STATE_SERVICE).on("enabledPortals", () => this.clearCache());
	}
	onCleanup() {
		this.detachEnabledPortalsListener?.();
		this.detachEnabledPortalsListener = null;
		this.clearCache();
	}
	getAvailablePortals(data) {
		this.evictExpiredEntries();
		const hostname = location.hostname;
		const { id, type } = data;
		const cacheKey = `${id}-${type}-${hostname}`;
		const cached = this.cache.get(cacheKey);
		if (cached) {
			this.cache.delete(cacheKey);
			this.cache.set(cacheKey, cached);
			return cached.results;
		}
		const results = createAvailablePortals({
			portals: this.portals,
			enabledPortals: resolveService(STATE_SERVICE).proxy.enabledPortals || [],
			hostname,
			id,
			type
		});
		this.cache.set(cacheKey, {
			results,
			timestamp: Date.now()
		});
		this.enforceCacheLimit();
		return results;
	}
	getAllPortals() {
		return getAllPortals(this.portals);
	}
	getAllSites() {
		return getAllPortalSites(this.portals);
	}
	clearCache() {
		this.cache.clear();
	}
	evictExpiredEntries() {
		if (this.cache.size === 0) return;
		const now = Date.now();
		for (const [key, entry] of this.cache.entries()) if (now - entry.timestamp >= this.cacheTtlMs) this.cache.delete(key);
	}
	enforceCacheLimit() {
		if (this.cache.size <= this.cacheMaxSize) return;
		const overflow = this.cache.size - this.cacheMaxSize;
		const keysToDelete = this.cache.keys();
		for (let i = 0; i < overflow; i += 1) {
			const oldestKey = keysToDelete.next().value;
			if (!oldestKey) break;
			this.cache.delete(oldestKey);
		}
	}
};
/** Constructed once from app/modules (composition root). */
function createPortalService() {
	return new PortalService();
}
var log$38 = Logger.scope("Preview");
var PreviewService = class {
	cache = /* @__PURE__ */ new Map();
	maxCacheSize = CACHE.PREVIEW_MAX_SIZE;
	inflightPreviewRequests = /* @__PURE__ */ new Map();
	lastPreviewRequestAt = Number.NEGATIVE_INFINITY;
	previewCooldownUntil = 0;
	onBootstrap() {
		log$38.debug("Preview service bootstrapped");
	}
	onCleanup() {
		this.clearCache();
		this.inflightPreviewRequests.clear();
		this.previewCooldownUntil = 0;
		this.lastPreviewRequestAt = Number.NEGATIVE_INFINITY;
	}
	async getPreviewBlob(id, type, previewSlug, signal) {
		const url = this.getPreviewUrl(id, type, previewSlug);
		const inflight = this.inflightPreviewRequests.get(url);
		if (inflight) return inflight;
		const now = Date.now();
		if (now < this.previewCooldownUntil) {
			const err = /* @__PURE__ */ new Error("Preview cooldown active");
			err.status = 403;
			throw err;
		}
		const elapsed = now - this.lastPreviewRequestAt;
		if (elapsed < TIMING.POLITE_DELAY_MS) {
			const err = /* @__PURE__ */ new Error("Rate limited");
			err.retryAfterMs = TIMING.POLITE_DELAY_MS - elapsed;
			throw err;
		}
		this.lastPreviewRequestAt = now;
		const request = fetchBlob(url, {}, signal).catch((error) => {
			if (error?.status === 403 || error?.status === 429 || error?.status === 503) this.previewCooldownUntil = Date.now() + TIMING.RATE_LIMIT_BACKOFF_MS;
			throw error;
		}).finally(() => {
			if (this.inflightPreviewRequests.get(url) === request) this.inflightPreviewRequests.delete(url);
		});
		this.inflightPreviewRequests.set(url, request);
		return request;
	}
	getPreviewUrl(id, type, previewSlug) {
		if (previewSlug) return `${EXTERNAL_URLS.FOURHOI_BASE}/${previewSlug.toLowerCase()}/preview.mp4`;
		if (type === "fc2") return `${EXTERNAL_URLS.FOURHOI_BASE}/fc2-ppv-${id}/preview.mp4`;
		return `${EXTERNAL_URLS.FOURHOI_BASE}/${id.toLowerCase()}/preview.mp4`;
	}
	async checkExistence(id, type, previewSlug) {
		const url = this.getPreviewUrl(id, type, previewSlug);
		return new Promise((resolve) => {
			GM_xmlhttpRequest({
				method: "HEAD",
				url,
				timeout: 5e3,
				onload: (response) => resolve(response.status >= 200 && response.status < 300),
				onerror: () => resolve(false),
				ontimeout: () => resolve(false)
			});
		});
	}
	getCachedElement(id) {
		const entry = this.cache.get(id);
		if (entry) {
			entry.timestamp = Date.now();
			return entry.element;
		}
	}
	cacheElement(id, element) {
		if (this.cache.has(id)) {
			this.cache.get(id).timestamp = Date.now();
			return;
		}
		if (this.cache.size >= this.maxCacheSize) {
			const oldest = Array.from(this.cache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp)[0];
			if (oldest) {
				this.disposeElement(oldest[1].element);
				this.cache.delete(oldest[0]);
			}
		}
		this.cache.set(id, {
			url: element.src,
			element,
			timestamp: Date.now()
		});
	}
	clearCache() {
		this.cache.forEach((item) => this.disposeElement(item.element));
		this.cache.clear();
		log$38.debug("Cache cleared");
	}
	disposeElement(element) {
		if (element instanceof HTMLVideoElement) {
			const src = element.src || element.currentSrc || element.getAttribute("src");
			element.pause();
			element.removeAttribute("src");
			try {
				element.load();
			} catch {}
			element.remove();
			if (src?.startsWith("blob:")) URL.revokeObjectURL(src);
		} else element.remove();
	}
};
/** Constructed once from app/modules (composition root). */
function createPreviewService() {
	return new PreviewService();
}
/**
* Fetch HTML content from a URL and parse it into a Document.
* Returns structured result so callers can distinguish "empty" from "failed".
* HTTP errors (with status codes) are re-thrown so callers can handle 429, 403, etc.
*/
var fetchAndParseHtml = async (url, options) => {
	try {
		const rawHtml = await http(url, {
			type: "text",
			...options
		});
		return { doc: new DOMParser().parseFromString(rawHtml, "text/html") };
	} catch (e) {
		if (e && typeof e === "object" && "status" in e) throw e;
		return {
			doc: null,
			error: { message: e instanceof Error ? e.message : "Unknown fetch error" }
		};
	}
};
var resolveScraperHealthUrl = (target, paipanconBaseUrl) => {
	if (target === "sukebei") return "https://sukebei.nyaa.si";
	if (target === "fd2") return "https://fd2ppv.cc";
	return paipanconBaseUrl;
};
var detectScraperHealthStatus = (html) => {
	const doc = new DOMParser().parseFromString(html, "text/html");
	if (doc.title && CLOUDFLARE_INDICATORS.some((indicator) => doc.title.includes(indicator))) return "cf";
	return "ok";
};
var groupMagnetRequestItems = (items) => {
	const grouped = /* @__PURE__ */ new Map();
	items.forEach((item) => {
		const key = item.type || "fc2";
		if (!grouped.has(key)) grouped.set(key, []);
		grouped.get(key).push(item);
	});
	return grouped;
};
var chunkMagnetIds = (ids) => {
	const chunkSize = NETWORK.CHUNK_SIZE;
	const chunks = [];
	for (let index = 0; index < ids.length; index += chunkSize) chunks.push(ids.slice(index, index + chunkSize));
	return chunks;
};
var createPreviewExistsCacheKey = (fc2Id) => `has_previews_${fc2Id}`;
var log$37 = Logger.scope("ActressProvider");
var ActressProvider = class {
	async fetchActressFromFD2(id) {
		const cacheKey = `actress_${id}`;
		const cached = await resolveService(DATA_SERVICE).cache.get(cacheKey);
		if (cached) {
			const cleanCached = MediaUtils.cleanActressName(cached);
			if (cleanCached) {
				log$37.debug(`Actress cache hit: ${id} = ${cleanCached}`);
				return cleanCached;
			} else {
				log$37.warn(`Removing invalid cached actress for ${id}: ${cached}`);
				await resolveService(DATA_SERVICE).cache.delete(cacheKey);
			}
		}
		const backoffEnd = Number(Storage.get(STORAGE_KEYS.FD2_BACKOFF_UNTIL, 0));
		if (Date.now() < backoffEnd) {
			log$37.debug(`Skipping FD2 request, backoff active (${Math.ceil((backoffEnd - Date.now()) / 1e3)}s remaining)`);
			return null;
		}
		const now = Date.now();
		const lastFetch = Number(Storage.get(STORAGE_KEYS.LAST_ACTRESS_FETCH, 0));
		const waitTime = Math.max(0, lastFetch + TIMING.ACTRESS_BASE_DELAY_MS + Math.random() * TIMING.ACTRESS_RANDOM_DELAY_MS - now);
		if (waitTime > 0) await Utils.sleep(waitTime);
		Storage.set(STORAGE_KEYS.LAST_ACTRESS_FETCH, Date.now());
		try {
			const url = EXTERNAL_URLS.FD2PPV.replace("{id}", id);
			const { doc, error } = await fetchAndParseHtml(url, {
				headers: getBypassHeaders(url),
				timeout: TIMING.HTTP_FD2_TIMEOUT
			});
			if (!doc) {
				log$37.debug(`Failed to fetch FD2 page: ${error?.message || "unknown"}`);
				return null;
			}
			if (doc.title && CLOUDFLARE_INDICATORS.some((s) => doc.title.includes(s))) {
				log$37.warn("FD2PPV Cloudflare detected, backing off 5m");
				Storage.set(STORAGE_KEYS.FD2_BACKOFF_UNTIL, Date.now() + TIMING.FD2_BACKOFF_MS);
				return null;
			}
			const actressLinks = Array.from(doc.querySelectorAll([
				".artist-info-card .artist-name a",
				".artist-name a",
				"a.artistUrl",
				"h3.artist-name a"
			].join(",")));
			let actress = null;
			for (const link of actressLinks) {
				const cleaned = MediaUtils.cleanActressName(link.textContent);
				if (cleaned) {
					actress = cleaned;
					break;
				}
			}
			if (actress) {
				await resolveService(DATA_SERVICE).cache.set(cacheKey, actress);
				log$37.info(`Fetched actress: ${id} = ${actress}`);
			}
			return actress;
		} catch (e) {
			const status = getHttpStatus(e);
			if (status === 403 || status === 429 || status === 503) {
				log$37.warn(`FD2PPV error (${status}), backing off 5m`);
				Storage.set(STORAGE_KEYS.FD2_BACKOFF_UNTIL, Date.now() + TIMING.FD2_BACKOFF_MS);
			} else log$37.error(`Failed to fetch actress: ${id}`, e);
			return null;
		}
	}
	resetBackoff() {
		Storage.delete(STORAGE_KEYS.FD2_BACKOFF_UNTIL);
		log$37.debug("FD2PPV backoff reset");
	}
};
var log$36 = Logger.scope("MagnetProvider");
var MagnetProvider = class {
	sukebeiQueue;
	constructor(sukebeiQueue) {
		this.sukebeiQueue = sukebeiQueue;
	}
	async fetchFromSukebei(chunk, onResult, traceId) {
		const queryParts = [];
		for (const id of chunk) {
			const dateParts = MediaUtils.parseDateIdParts(id);
			if (dateParts) {
				const sep = dateParts.brand === "CARIBBEANCOM" ? "-" : "_";
				queryParts.push(`${dateParts.date}${sep}${dateParts.serial}-${dateParts.short}`);
			} else queryParts.push(id);
		}
		const query = queryParts.join("|");
		const url = Config.SCRAPER_URLS.SUKEBEI_SEARCH.replace("{query}", encodeURIComponent(query));
		try {
			const { doc, error } = await fetchAndParseHtml(url);
			if (!doc) {
				log$36.warn(`Failed to fetch Sukebei page: ${error?.message || "unknown"}`, void 0, traceId);
				return /* @__PURE__ */ new Set();
			}
			if (doc.title && CLOUDFLARE_INDICATORS.some((s) => doc.title.includes(s))) {
				log$36.warn("Cloudflare blocked, backing off 60s", void 0, traceId);
				this.sukebeiQueue.triggerBackoff(TIMING.CLOUDFLARE_BACKOFF_MS, "Sukebei (Cloudflare)");
				return /* @__PURE__ */ new Set();
			}
			const rows = doc.querySelectorAll("table.torrent-list tbody tr");
			const foundIds = /* @__PURE__ */ new Set();
			const regexes = chunk.map((id) => {
				const escaped = id.toUpperCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
				const parts = MediaUtils.parseDateIdParts(id);
				return {
					id,
					regex: new RegExp(`(?<=[^0-9a-zA-Z]|^)${escaped.replace(/[-_\s]/g, "[-_\\s]")}(?=[^0-9a-zA-Z]|$)`, "i"),
					parts,
					searchId: id.toUpperCase()
				};
			});
			for (let i = 0; i < rows.length; i++) {
				const row = rows[i];
				if (!row) continue;
				const magnetAnchor = row.querySelector("a[href^='magnet:?']");
				const linkAnchor = row.querySelector("a[href*=\"/view/\"]") || row.querySelector("td:nth-child(2) a");
				if (magnetAnchor && linkAnchor) {
					const title = linkAnchor.textContent?.trim().toUpperCase() || "";
					const magnet = magnetAnchor.href;
					for (const { id, regex, searchId, parts } of regexes) {
						if (foundIds.has(id)) continue;
						const match = title.match(regex);
						let matchedIndex = match?.index ?? null;
						let isFlexibleMatch = false;
						if (!match && parts) {
							const { date, serial, short } = parts;
							const hasDate = title.includes(date);
							const hasSerial = title.includes(serial);
							if (hasDate && hasSerial) {
								const dateIdx = title.indexOf(date);
								const serialIdx = title.indexOf(serial);
								if (Math.abs(dateIdx - serialIdx) < TIMING.TITLE_MATCH_PROXIMITY) {
									if (title.includes(short) || !/[A-Z]{2,}-\d+/.test(title.replace(date, "").replace(serial, ""))) {
										matchedIndex = dateIdx;
										isFlexibleMatch = true;
									}
								}
							}
						}
						if (match || isFlexibleMatch) {
							if (!isFlexibleMatch) {
								if (!match) continue;
								const idx = matchedIndex;
								const matchedStr = match[0];
								if (idx === null || !matchedStr) continue;
								const before = title[idx - 1], after = title[idx + matchedStr.length];
								const isDigit = (c) => c !== void 0 && c >= "0" && c <= "9";
								if (isDigit(searchId[0]) && isDigit(before)) continue;
								if (isDigit(searchId[searchId.length - 1]) && isDigit(after)) continue;
							}
							onResult(id, magnet);
							foundIds.add(id);
						}
					}
				}
			}
			log$36.info(`Sukebei: ${foundIds.size}/${chunk.length} found`, void 0, traceId);
			this.sukebeiQueue.resetBackoff();
			return foundIds;
		} catch (e) {
			if (getHttpStatus(e) === 429) this.sukebeiQueue.triggerBackoff(TIMING.RATE_LIMIT_BACKOFF_MS, "Sukebei (429)");
			else log$36.error("Sukebei fetch error", e, traceId);
			return /* @__PURE__ */ new Set();
		}
	}
};
var log$35 = Logger.scope("PreviewProvider");
/** Check if parsed HTML is a Cloudflare challenge page */
var isCloudflarePage = (doc) => {
	const title = doc.title || "";
	if (CLOUDFLARE_INDICATORS.some((indicator) => title.includes(indicator))) return true;
	if (doc.querySelector("form#challenge-form, #challenge-running, #challenge-stage")) return true;
	const bodyText = doc.body?.textContent || "";
	if (bodyText.length < 1e3 && CLOUDFLARE_INDICATORS.some((i) => bodyText.includes(i))) return true;
	return false;
};
/** Check if an error response body is a Cloudflare challenge page */
var isCloudflareErrorResponse = (err) => {
	if (err && typeof err === "object" && "response" in err) {
		const html = err.response;
		if (typeof html === "string" && html.length > 0) {
			if (CLOUDFLARE_INDICATORS.some((indicator) => html.includes(indicator))) return true;
			if (html.includes("challenge-form") || html.includes("challenge-running")) return true;
		}
	}
	return false;
};
var PreviewProvider = class {
	paipanconQueue;
	constructor(paipanconQueue) {
		this.paipanconQueue = paipanconQueue;
	}
	async fetchExtraPreviews(fc2Id) {
		try {
			const normalizedId = fc2Id.padStart(7, "0");
			const idPattern = `${PATTERNS.FC2_PPV_PREFIX}${normalizedId}`;
			const { doc } = await fetchAndParseHtml(Config.SCRAPER_URLS.PAIPANCON_DETAIL.replace("{id}", normalizedId));
			if (!doc) return {
				items: [],
				error: "network-error"
			};
			if (isCloudflarePage(doc)) {
				log$35.warn(`Cloudflare challenge detected for ${fc2Id}`);
				return {
					items: [],
					error: "cf-blocked"
				};
			}
			const results = [];
			const blacklist = PREVIEW_BLACKLIST;
			doc.querySelectorAll("img").forEach((img) => {
				let src = img.getAttribute("src");
				if (src) {
					const lowSrc = src.toLowerCase();
					const isBad = blacklist.some((key) => lowSrc.includes(key));
					const isRelevant = src.includes(idPattern) || src.includes(fc2Id);
					if (!isBad && isRelevant) {
						if (src.startsWith("/")) src = Config.SCRAPER_URLS.PAIPANCON_BASE + src;
						results.push({
							type: "image",
							src
						});
					}
				}
			});
			doc.querySelectorAll("video").forEach((video) => {
				let src = video.getAttribute("src") || video.querySelector("source")?.getAttribute("src");
				if (src) {
					if (src.includes(idPattern) || src.includes(fc2Id)) {
						if (src.startsWith("/")) src = Config.SCRAPER_URLS.PAIPANCON_BASE + src;
						results.push({
							type: "video",
							src
						});
					}
				}
			});
			return {
				items: results,
				error: results.length === 0 ? "not-found" : void 0
			};
		} catch (e) {
			const status = getHttpStatus(e);
			if (status === 404) {
				log$35.debug(`No previews found on Paipancon for ${fc2Id}`);
				return {
					items: [],
					error: "not-found"
				};
			}
			if (status === 429 || status === 503) {
				this.paipanconQueue.triggerBackoff(TIMING.RATE_LIMIT_BACKOFF_MS, `Paipancon (${status})`);
				return {
					items: [],
					error: "rate-limited"
				};
			}
			if (status === 403 || isCloudflareErrorResponse(e)) {
				log$35.warn(`Cloudflare/block detected for ${fc2Id} (Status: ${status || "Unknown"})`);
				return {
					items: [],
					error: "cf-blocked"
				};
			}
			log$35.warn(`Failed to fetch extra previews: ${fc2Id} (Status: ${status || "Unknown"})`);
			return {
				items: [],
				error: "network-error"
			};
		}
	}
};
var log$34 = Logger.scope("ScraperQueue");
var DEFAULT_MAX_QUEUE_SIZE = 200;
/**
* Rate-limited task queue with exponential backoff for scraper requests.
* Backoff state is stored via GM_setValue (cross-tab shared) to prevent
* one tab from undoing another's rate-limit backoff.
*/
var ScraperQueue = class {
	maxConcurrent;
	scope;
	maxQueueSize;
	queue = [];
	queuedKeys = /* @__PURE__ */ new Set();
	activeRequests = 0;
	constructor(maxConcurrent, scope, maxQueueSize = DEFAULT_MAX_QUEUE_SIZE) {
		this.maxConcurrent = maxConcurrent;
		this.scope = scope;
		this.maxQueueSize = maxQueueSize;
	}
	/** Number of tasks waiting to run. */
	get size() {
		return this.queue.length;
	}
	/** Number of requests currently in flight. */
	get pending() {
		return this.activeRequests;
	}
	getKey(suffix) {
		return `scraper_backoff_${this.scope}_${suffix}`;
	}
	getBackoff() {
		return Number(GM_getValue(this.getKey("until"), 0));
	}
	getBackoffLevel() {
		return Number(GM_getValue(this.getKey("level"), 0));
	}
	triggerBackoff(baseDelay, source = "Unknown") {
		const currentLevel = this.getBackoffLevel();
		const exponentialDelay = baseDelay * Math.pow(2, currentLevel);
		const finalDelay = Math.min(exponentialDelay, TIMING.MAX_BACKOFF_MS);
		const totalDuration = finalDelay + Math.min(3e3, finalDelay * .1) * Math.random();
		const until = Date.now() + totalDuration;
		const nextLevel = Math.min(currentLevel + 1, 6);
		GM_setValue(this.getKey("until"), until);
		GM_setValue(this.getKey("level"), nextLevel);
		log$34.warn(`[${this.scope}] Backoff triggered by ${source} for ${Math.ceil(totalDuration / 1e3)}s (L${nextLevel})`);
	}
	resetBackoff() {
		if (this.getBackoffLevel() > 0) {
			GM_setValue(this.getKey("level"), 0);
			log$34.debug(`[${this.scope}] Backoff level reset`);
		}
	}
	resetFullBackoff() {
		GM_deleteValue(this.getKey("until"));
		GM_deleteValue(this.getKey("level"));
		log$34.debug(`[${this.scope}] Full backoff reset`);
	}
	checkBackoff() {
		return Date.now() >= this.getBackoff();
	}
	async waitBackoff() {
		const backoffUntil = this.getBackoff();
		const now = Date.now();
		if (now < backoffUntil) await Utils.sleep(backoffUntil - now);
	}
	/**
	* Adds a task to the queue. Returns false when a task with the same key
	* is already queued or when the bounded queue is full.
	*/
	enqueue(task, key) {
		if (key !== void 0 && key !== "") {
			if (this.queuedKeys.has(key)) return false;
			this.queuedKeys.add(key);
			this.queue.push({
				key,
				run: () => {
					this.queuedKeys.delete(key);
					return task();
				}
			});
		} else this.queue.push({ run: task });
		if (this.queue.length > this.maxQueueSize) {
			const dropped = this.queue.pop();
			if (dropped?.key) this.queuedKeys.delete(dropped.key);
			log$34.warn(`[${this.scope}] Queue full (${this.maxQueueSize}), dropped newest queued task`);
			return false;
		}
		this.processQueue();
		return true;
	}
	/** Drops queued (non-active) tasks. In-flight requests are not interrupted. */
	clear() {
		this.queue.length = 0;
		this.queuedKeys.clear();
	}
	async processQueue() {
		if (this.activeRequests >= this.maxConcurrent || this.queue.length === 0) return;
		this.activeRequests++;
		const task = this.queue.shift();
		try {
			await task.run();
		} catch (error) {
			log$34.warn(`[${this.scope}] Task failed`, error);
		} finally {
			this.activeRequests--;
			this.processQueue();
		}
	}
};
var log$33 = Logger.scope("ScraperService");
var ScraperService = class {
	MAX_MAGNET_CONCURRENT = 3;
	MAX_PREVIEW_CONCURRENT = 2;
	magnetQueue;
	previewQueue;
	magnetProvider;
	previewProvider;
	actressProvider;
	constructor() {
		this.magnetQueue = new ScraperQueue(this.MAX_MAGNET_CONCURRENT, "sukebei");
		this.previewQueue = new ScraperQueue(this.MAX_PREVIEW_CONCURRENT, "paipancon");
		this.magnetProvider = new MagnetProvider(this.magnetQueue);
		this.previewProvider = new PreviewProvider(this.previewQueue);
		this.actressProvider = new ActressProvider();
	}
	onBootstrap() {
		log$33.debug("Scraper service bootstrapped, queues ready");
	}
	onCleanup() {
		this.magnetQueue.resetFullBackoff();
		this.previewQueue.resetFullBackoff();
		this.magnetQueue.clear();
		this.previewQueue.clear();
		log$33.debug("Scraper service cleaned up, queues reset");
	}
	notifyBlocked(target, url) {
		const { t } = Localization;
		CoreEvents.emit(AppEvents.SHOW_TOAST, {
			message: t("alertScraperBlocked", { target }),
			type: "warn",
			options: {
				duration: TIMING.SCRAPER_TOAST_DURATION,
				action: {
					label: t("btnGoVerify"),
					onClick: () => BrowserService.openTab(url)
				}
			}
		});
	}
	async testHealth(target) {
		const url = resolveScraperHealthUrl(target, Config.SCRAPER_URLS.PAIPANCON_BASE);
		try {
			return detectScraperHealthStatus(await http(url, {
				type: "text",
				timeout: NETWORK.HEALTH_CHECK_TIMEOUT
			}));
		} catch (error) {
			const status = getHttpStatus(error);
			if (status === 429) return "429";
			if (status === 403 || status === 503) return "cf";
			return "error";
		}
	}
	async fetchMagnets(items, onResult) {
		if (!items?.length) return;
		const traceId = Logger.traceId;
		log$33.info(`Starting magnet fetch for ${items.length} items`, { ids: items.map((item) => item.id) }, traceId);
		const tasks = [];
		for (const [type, itemList] of groupMagnetRequestItems(items)) for (const chunk of chunkMagnetIds(itemList.map((item) => item.id))) tasks.push(this.processMagnetChunk(chunk, type, onResult, traceId));
		await Promise.all(tasks);
	}
	processMagnetChunk(chunk, _type, onResult, traceId) {
		return new Promise((resolve) => {
			if (!this.magnetQueue.enqueue(async () => {
				const foundIds = /* @__PURE__ */ new Set();
				try {
					await this.magnetQueue.waitBackoff();
					const delay = TIMING.MAGNET_BASE_DELAY_MS + Math.random() * TIMING.MAGNET_RANDOM_DELAY_MS;
					await Utils.sleep(delay);
					(await this.magnetProvider.fetchFromSukebei(chunk, (id, url) => {
						foundIds.add(id);
						onResult(id, url);
					}, traceId)).forEach((id) => foundIds.add(id));
				} catch (error) {
					log$33.error("Magnet fetch task failed", error, traceId);
					if (await this.testHealth("sukebei") === "cf") this.notifyBlocked("Sukebei", "https://sukebei.nyaa.si");
				} finally {
					chunk.forEach((id) => {
						if (!foundIds.has(id)) onResult(id, null);
					});
					resolve();
				}
			}, chunk.join(","))) {
				chunk.forEach((id) => onResult(id, null));
				resolve();
			}
		});
	}
	async fetchActressFromFD2(id) {
		return this.actressProvider.fetchActressFromFD2(id);
	}
	resetFD2Backoff() {
		this.actressProvider.resetBackoff();
	}
	async fetchExtraPreviews(fc2Id) {
		return this.previewProvider.fetchExtraPreviews(fc2Id);
	}
	async checkPreviewExists(fc2Id) {
		const cacheKey = createPreviewExistsCacheKey(fc2Id);
		const cached = await resolveService(DATA_SERVICE).cache.get(cacheKey);
		if (cached !== null) return cached === true;
		return new Promise((resolve) => {
			if (!this.previewQueue.enqueue(async () => {
				if (!this.previewQueue.checkBackoff()) {
					resolve(false);
					return;
				}
				const jitter = Math.random() * TIMING.PREVIEW_JITTER_MS;
				await Utils.sleep(TIMING.POLITE_DELAY_MS + jitter);
				try {
					const exists = (await this.fetchExtraPreviews(fc2Id)).items.length > 0;
					if (exists) this.previewQueue.resetBackoff();
					await resolveService(DATA_SERVICE).cache.set(cacheKey, exists);
					resolve(exists);
				} catch (error) {
					log$33.trace(`Preview check failed for ${fc2Id}`, error);
					resolve(false);
				}
			}, fc2Id)) resolve(false);
		});
	}
};
/** Constructed once from app/modules (composition root). */
function createScraperService() {
	return new ScraperService();
}
/** Detected page context — drives which enhancement pipeline (list/detail) activates */
var PageContext = /* @__PURE__ */ function(PageContext) {
	PageContext["Unknown"] = "unknown";
	PageContext["List"] = "list";
	PageContext["Detail"] = "detail";
	PageContext["User"] = "user";
	PageContext["Search"] = "search";
	return PageContext;
}({});
/** Run a promise without awaiting; log failures instead of swallowing. */
var fireAndForget = (promise, scope = "Async") => {
	promise.catch((error) => {
		Logger.scope(scope).error("Unhandled async failure", error);
	});
};
var SiteHostGuard = class {
	processedHosts = /* @__PURE__ */ new WeakSet();
	hostProcessedAttr;
	constructor(hostProcessedAttr) {
		this.hostProcessedAttr = hostProcessedAttr;
	}
	isAlreadyProcessed(host) {
		if (this.processedHosts.has(host)) return true;
		if (host.getAttribute("data-enh-processed") === "true") {
			this.markProcessed(host);
			return true;
		}
		if (host.hasAttribute(this.hostProcessedAttr) || host.classList.contains(Config.CLASSES.cardRebuilt)) {
			this.markProcessed(host);
			return true;
		}
		const processedDescendants = host.querySelectorAll(`.${Config.CLASSES.processedCard}`);
		if (processedDescendants.length > 0) {
			if (processedDescendants.length > 1) for (let i = 1; i < processedDescendants.length; i++) processedDescendants[i]?.remove();
			host.classList.add(Config.CLASSES.cardRebuilt);
			this.markProcessed(host);
			return true;
		}
		return false;
	}
	isNestedInsideProcessed(node) {
		return Boolean(node.parentElement?.closest(`[${this.hostProcessedAttr}="1"], [data-enh-rebuilding="true"], .${Config.CLASSES.cardRebuilt}`));
	}
	markProcessed(host) {
		this.processedHosts.add(host);
		host.setAttribute(this.hostProcessedAttr, "1");
	}
	isKnownHost(host) {
		return this.processedHosts.has(host);
	}
	clear() {
		this.processedHosts = /* @__PURE__ */ new WeakSet();
	}
};
var log$32 = Logger.scope("DomObserver");
var DomObserverService = class {
	observer = null;
	handlers = /* @__PURE__ */ new Set();
	isInitialized = false;
	constructor() {
		if (typeof document !== "undefined") if (document.body) this.init();
		else document.addEventListener("DOMContentLoaded", () => this.init(), { once: true });
	}
	init() {
		if (this.isInitialized || typeof MutationObserver === "undefined" || !document.body) return;
		try {
			this.observer = new MutationObserver((mutations) => this.handleMutations(mutations));
			this.observer.observe(document.body, {
				childList: true,
				subtree: true
			});
			this.isInitialized = true;
			log$32.debug("Global DomObserver initialized on document.body");
		} catch (e) {
			log$32.error("Failed to initialize MutationObserver", e);
		}
	}
	handleMutations(mutations) {
		const added = [];
		const removed = [];
		for (const m of mutations) {
			if (m.addedNodes.length > 0) m.addedNodes.forEach((node) => {
				if (typeof HTMLElement !== "undefined" && node instanceof HTMLElement) added.push(node);
			});
			if (m.removedNodes.length > 0) m.removedNodes.forEach((node) => {
				if (typeof HTMLElement !== "undefined" && node instanceof HTMLElement) removed.push(node);
			});
		}
		if (added.length > 0 || removed.length > 0) this.handlers.forEach((handler) => {
			try {
				handler(added, removed);
			} catch (e) {
				log$32.error("DomMutationHandler failed", e);
			}
		});
	}
	subscribe(handler) {
		this.handlers.add(handler);
		if (!this.isInitialized) this.init();
		return () => {
			this.handlers.delete(handler);
		};
	}
	disconnect() {
		if (this.observer) {
			this.observer.disconnect();
			this.observer = null;
		}
		this.handlers.clear();
		this.isInitialized = false;
	}
};
var DomObserver = new DomObserverService();
var SiteListObserver = class {
	observers = [];
	cardSelector;
	containerSelector;
	process;
	unsubs = [];
	initialized = false;
	constructor(cardSelector, containerSelector, process) {
		this.cardSelector = cardSelector;
		this.containerSelector = containerSelector;
		this.process = process;
	}
	init(mutationObserverTarget, observeMutations = true) {
		if (this.initialized) return this.observers;
		this.initialized = true;
		const obs = this.setupListMutationObserver();
		if (mutationObserverTarget === "container" && !!this.containerSelector) this.setupContainerObserver();
		else if (observeMutations !== false) this.unsubs.push(DomObserver.subscribe((added, _removed) => {
			this.handleAddedNodes(added);
		}));
		this.observers.push(obs);
		return this.observers;
	}
	setupListMutationObserver() {
		return new MutationObserver((muts) => {
			const added = [];
			for (const m of muts) m.addedNodes.forEach((n) => {
				if (n.nodeType === 1) added.push(n);
			});
			if (added.length > 0) this.handleAddedNodes(added);
		});
	}
	handleAddedNodes(nodes) {
		const added = /* @__PURE__ */ new Set();
		for (const el of nodes) {
			if (!this.isInListScope(el)) continue;
			if (el.matches(this.cardSelector)) added.add(el);
			else {
				el.querySelectorAll(this.cardSelector).forEach((child) => added.add(child));
				const closestCard = el.closest(this.cardSelector);
				if (closestCard) added.add(closestCard);
			}
		}
		if (added.size > 0) this.process(Array.from(added));
	}
	isInListScope(el) {
		if (!this.containerSelector) return true;
		return el.matches(this.containerSelector) || el.closest(this.containerSelector) !== null || el.querySelector(this.containerSelector) !== null;
	}
	setupContainerObserver() {
		if (!this.containerSelector) return;
		const selector = this.containerSelector;
		const observedContainers = /* @__PURE__ */ new WeakSet();
		const listObs = this.setupListMutationObserver();
		const observe = (container) => {
			if (observedContainers.has(container)) return;
			observedContainers.add(container);
			listObs.observe(container, {
				childList: true,
				subtree: true
			});
		};
		const scan = (el) => {
			const found = [];
			if (el.matches(selector)) found.push(el);
			el.querySelectorAll(selector).forEach((c) => found.push(c));
			return found;
		};
		document.querySelectorAll(selector).forEach((c) => observe(c));
		this.unsubs.push(DomObserver.subscribe((added, _removed) => {
			const newContainers = /* @__PURE__ */ new Set();
			for (const el of added) {
				if (el.classList.contains(Config.CLASSES.processedCard) || el.closest(`.${Config.CLASSES.processedCard}`)) continue;
				scan(el).forEach((c) => newContainers.add(c));
			}
			if (newContainers.size === 0) return;
			newContainers.forEach((c) => observe(c));
			const candidates = /* @__PURE__ */ new Set();
			newContainers.forEach((c) => {
				if (c.matches(this.cardSelector)) candidates.add(c);
				c.querySelectorAll(this.cardSelector).forEach((card) => candidates.add(card));
			});
			if (candidates.size > 0) this.process(Array.from(candidates));
		}));
		this.observers.push(listObs);
	}
	disconnect() {
		this.observers.forEach((o) => o.disconnect());
		this.observers = [];
		this.unsubs.forEach((fn) => fn());
		this.unsubs = [];
		this.initialized = false;
	}
};
var SourceKeyManager = class SourceKeyManager {
	inFlight = /* @__PURE__ */ new Set();
	history = /* @__PURE__ */ new Map();
	windowMs;
	limit;
	pruneCounter = 0;
	static PRUNE_EVERY = 50;
	constructor(windowMs = 6e4, limit = 3e3) {
		this.windowMs = windowMs;
		this.limit = limit;
	}
	isProcessing(key) {
		return this.inFlight.has(key);
	}
	isRecentlyProcessed(key) {
		const now = Date.now();
		this.prune(now);
		const timestamp = this.history.get(key);
		return typeof timestamp === "number" && now - timestamp < this.windowMs;
	}
	markInFlight(key) {
		if (this.inFlight.size >= this.limit) this.inFlight.clear();
		this.inFlight.add(key);
	}
	markProcessed(key) {
		const now = Date.now();
		this.history.delete(key);
		this.history.set(key, now);
		this.inFlight.delete(key);
		this.pruneCounter += 1;
		if (this.pruneCounter >= SourceKeyManager.PRUNE_EVERY) {
			this.pruneCounter = 0;
			this.prune(now);
		}
	}
	release(key, markProcessed = false) {
		this.inFlight.delete(key);
		if (markProcessed) this.markProcessed(key);
	}
	clear() {
		this.inFlight.clear();
		this.history.clear();
	}
	prune(now) {
		const expireBefore = now - this.windowMs;
		for (const [key, timestamp] of this.history.entries()) if (timestamp < expireBefore) this.history.delete(key);
		if (this.history.size <= this.limit) return;
		const overflow = this.history.size - this.limit;
		const keys = this.history.keys();
		for (let i = 0; i < overflow; i += 1) {
			const oldest = keys.next().value;
			if (!oldest) break;
			this.history.delete(oldest);
		}
	}
};
var log$31 = Logger.scope("Site");
var BaseSite = class {
	config;
	observers = [];
	listObserver = null;
	activeContext = PageContext.Unknown;
	_unsubs = [];
	hostProcessedAttr = "data-enh-card-host";
	sourceKeyAttr = "data-enh-source-key";
	sourceKeyManager = new SourceKeyManager();
	hostGuard = new SiteHostGuard(this.hostProcessedAttr);
	constructor(config) {
		this.config = config;
	}
	clearSubscriptions() {
		this._unsubs.forEach((fn) => fn());
		this._unsubs = [];
	}
	async init() {
		try {
			log$31.debug(`Initializing ${this.config.name}`);
			this.clearSubscriptions();
			this.config.context = {
				supjavSortByViews: resolveService(STATE_SERVICE).proxy.supjavSortByViews,
				getSetting: (key) => resolveService(STATE_SERVICE).proxy[key]
			};
			if (this.config.onBeforeInit) await this.config.onBeforeInit();
			this.activeContext = this.detectContext();
			if (this.config.list && ([PageContext.List, PageContext.Search].includes(this.activeContext) || this.activeContext === PageContext.Detail && this.config.listOnDetail)) await this.initListMode();
			if (this.config.detail && this.activeContext === PageContext.Detail) this.initDetailMode();
			if (this.config.customInit) this.config.customInit();
			if (this.config.onInit) await this.config.onInit();
			if (this.config.onAfterInit) await this.config.onAfterInit();
			this._unsubs.push(resolveService(STATE_SERVICE).on(({ prop }) => {
				if ([
					"hideNoMagnet",
					"enableMagnets",
					"hideCensored",
					"hideViewed"
				].includes(prop)) this.refreshVisibility();
			}));
			this._unsubs.push(CoreEvents.on(AppEvents.HISTORY_LOADED, () => {
				this.refreshHistoryState();
				this.refreshVisibility();
			}));
			this.refreshHistoryState();
		} catch (error) {
			log$31.error(`Init failed for ${this.config.name}`, error);
			throw error;
		}
	}
	refreshVisibility() {
		CoreEvents.emit(AppEvents.VISIBILITY_REFRESH_NEEDED, {});
	}
	refreshHistoryState() {
		CoreEvents.emit(AppEvents.HISTORY_STATUS_NEEDED, {});
	}
	async initListMode() {
		if (!this.config.list) return;
		const list = this.config.list;
		const process = async (nodes) => {
			for (const c of nodes) try {
				const host = c;
				if (list.containerSelector && !c.closest(list.containerSelector)) continue;
				if (this.hostGuard.isNestedInsideProcessed(host)) continue;
				if (this.hostGuard.isAlreadyProcessed(host) || host.hasAttribute("data-enh-rebuilding")) continue;
				if (list.shouldProcessCard && !list.shouldProcessCard(host)) continue;
				const sourceKey = list.identityKey ? list.identityKey(host)?.trim() || null : null;
				if (sourceKey) {
					if (this.sourceKeyManager.isProcessing(sourceKey) || this.sourceKeyManager.isRecentlyProcessed(sourceKey)) continue;
					this.sourceKeyManager.markInFlight(sourceKey);
					host.setAttribute(this.sourceKeyAttr, sourceKey);
				}
				host.setAttribute("data-enh-rebuilding", "true");
				try {
					await this.processCard(host);
				} catch (e) {
					log$31.error("processCard failed", e);
					host.removeAttribute("data-enh-rebuilding");
					const k = host.getAttribute(this.sourceKeyAttr);
					if (k) this.sourceKeyManager.release(k, false);
					host.removeAttribute(this.sourceKeyAttr);
				}
			} catch (e) {
				log$31.error("Card iteration error", e);
			}
		};
		const initialNodes = Array.from(document.querySelectorAll(list.cardSelector));
		if (initialNodes.length > 0) await process(initialNodes);
		this.listObserver = new SiteListObserver(list.cardSelector, list.containerSelector, (nodes) => {
			fireAndForget(process(nodes), "Site");
		});
		const obs = this.listObserver.init(list.mutationObserverTarget, list.observeMutations !== false);
		this.observers.push(...obs);
	}
	initDetailMode() {
		if (!this.config.detail) return;
		const detail = this.config.detail;
		const check = () => {
			const target = document.querySelector(detail.triggerSelector || detail.mainImageSelector || "");
			if (target && !target.hasAttribute("data-enh-processed")) {
				target.setAttribute("data-enh-processed", "true");
				if (detail.customDetailAction) Promise.resolve(detail.customDetailAction(target, obs)).then((result) => {
					if (result) CoreEvents.emit(AppEvents.DETAIL_ENHANCEMENT_REQUESTED, {
						siteName: this.config.name,
						config: result,
						observer: obs
					});
				}).catch((e) => {
					log$31.error("Detail action failed", e);
					target.removeAttribute("data-enh-processed");
				});
			}
		};
		const obs = new MutationObserver(check);
		check();
		obs.observe(document.body, {
			childList: true,
			subtree: true
		});
		this.observers.push(obs);
	}
	async processCard(card) {
		if (!this.config.list) return;
		const list = this.config.list;
		try {
			let data = list.extractor(card);
			if (data instanceof Promise) data = await data;
			if (!data) {
				card.removeAttribute("data-enh-rebuilding");
				const k = card.getAttribute(this.sourceKeyAttr);
				if (k) this.sourceKeyManager.release(k, false);
				card.removeAttribute(this.sourceKeyAttr);
				return;
			}
			if (!data.id || !data.type) {
				log$31.warn(`Invalid card data from ${this.config.name}: missing id or type`, data);
				card.removeAttribute("data-enh-rebuilding");
				const k = card.getAttribute(this.sourceKeyAttr);
				if (k) this.sourceKeyManager.release(k, false);
				card.removeAttribute(this.sourceKeyAttr);
				return;
			}
			card.setAttribute("data-enh-processed", "true");
			card.classList.add(Config.CLASSES.cardRebuilt);
			this.hostGuard.markProcessed(card);
			const gridContainerSelector = list.gridContainerSelector ?? list.containerSelector;
			markGridContainer(gridContainerSelector ? card.closest(gridContainerSelector) : null);
			const extraUi = list.getExtraUi ? list.getExtraUi(card) : {};
			CoreEvents.emit(AppEvents.CARD_DISCOVERED, {
				id: data.id,
				type: data.type,
				data,
				host: card,
				extraUi,
				postProcess: list.postProcess ? (final, newCard, classes) => {
					list.postProcess(card, final, newCard, data, classes);
				} : void 0
			});
			card.dataset.id = data.id;
			card.removeAttribute("data-enh-rebuilding");
			const k = card.getAttribute(this.sourceKeyAttr);
			if (k) this.sourceKeyManager.release(k, true);
		} catch (error) {
			log$31.error("processCard failed", error);
			card.removeAttribute("data-enh-rebuilding");
			const k = card.getAttribute(this.sourceKeyAttr);
			if (k) this.sourceKeyManager.release(k, false);
			card.removeAttribute(this.sourceKeyAttr);
		}
	}
	cleanup() {
		this.observers.forEach((o) => o.disconnect());
		this.observers = [];
		if (this.listObserver) {
			this.listObserver.disconnect();
			this.listObserver = null;
		}
		this.hostGuard.clear();
		this.sourceKeyManager.clear();
		this.clearSubscriptions();
		if (this.config.onCleanup) this.config.onCleanup();
	}
};
var siteConfigRegistry = /* @__PURE__ */ new Map();
function registerSiteConfig(name, config) {
	config.name = name;
	siteConfigRegistry.set(name, config);
}
var log$30 = Logger.scope("SiteService");
var urlWatchers = /* @__PURE__ */ new Set();
var originalPushState = null;
var originalReplaceState = null;
var notifyUrlWatchers = () => {
	urlWatchers.forEach((check) => {
		try {
			check();
		} catch (error) {
			log$30.error("URL watcher callback failed", error);
		}
	});
};
var installHistoryPatch = () => {
	if (originalPushState && originalReplaceState) return;
	originalPushState = history.pushState;
	originalReplaceState = history.replaceState;
	history.pushState = function(...args) {
		const result = originalPushState.apply(history, args);
		notifyUrlWatchers();
		return result;
	};
	history.replaceState = function(...args) {
		const result = originalReplaceState.apply(history, args);
		notifyUrlWatchers();
		return result;
	};
	window.addEventListener("popstate", notifyUrlWatchers);
};
var uninstallHistoryPatch = () => {
	if (!originalPushState || !originalReplaceState || urlWatchers.size > 0) return;
	history.pushState = originalPushState;
	history.replaceState = originalReplaceState;
	originalPushState = null;
	originalReplaceState = null;
	window.removeEventListener("popstate", notifyUrlWatchers);
};
var GenericSite = class extends BaseSite {
	detectContext() {
		if (this.config.detectContext) try {
			return this.config.detectContext();
		} catch (error) {
			log$30.error("Custom detectContext failed", error);
		}
		const path = window.location.pathname;
		if (path.includes("/search/") || window.location.search.includes("keyword=")) return PageContext.Search;
		if (path.includes("/v/") || path.includes("/detail/") || path.includes("/movie/") || path.endsWith(".html")) return PageContext.Detail;
		return PageContext.Unknown;
	}
};
var matchSiteConfig = (registry, hostname) => {
	for (const config of registry.values()) if (config.hostnames.some((hostnameMatcher) => typeof hostnameMatcher === "string" ? hostname.includes(hostnameMatcher) : hostnameMatcher.test(hostname))) return config;
	return null;
};
var installUrlWatcher = (check) => {
	installHistoryPatch();
	urlWatchers.add(check);
	return () => {
		urlWatchers.delete(check);
		uninstallHistoryPatch();
	};
};
var log$29 = Logger.scope("SiteService");
var SiteService = class {
	registry = /* @__PURE__ */ new Map();
	activeSite = null;
	currentUrl = location.href;
	disposeUrlWatcher = null;
	async onBootstrap() {
		log$29.debug("Bootstrapped, initiating site matching");
		this.registry = siteConfigRegistry;
		await this.bootstrap();
	}
	onCleanup() {
		this.disposeUrlWatcher?.();
		this.disposeUrlWatcher = null;
		this.activeSite?.cleanup();
	}
	async bootstrap() {
		const matchedConfig = matchSiteConfig(this.registry, location.hostname);
		if (!matchedConfig) {
			log$29.warn(`No site config matched for ${location.hostname}`);
			return;
		}
		log$29.info(`Matched site: ${matchedConfig.name}`);
		this.activeSite = new GenericSite(matchedConfig);
		CoreEvents.emit(AppEvents.SITE_RESET, { siteName: matchedConfig.name });
		try {
			await this.activeSite.init();
		} catch (error) {
			log$29.error(`Site init failed for ${matchedConfig.name}`, error);
		}
		this.disposeUrlWatcher?.();
		this.disposeUrlWatcher = installUrlWatcher(() => {
			if (location.href === this.currentUrl) return;
			this.currentUrl = location.href;
			log$29.debug("URL change detected");
			this.activeSite?.cleanup();
			CoreEvents.emit(AppEvents.SITE_RESET, { siteName: matchedConfig.name });
			this.activeSite?.init().then(() => {
				CoreEvents.emit(AppEvents.SITE_READY, { siteName: matchedConfig.name });
			}).catch((error) => {
				log$29.error(`Re-init failed for ${matchedConfig.name}`, error);
			});
		});
		CoreEvents.emit(AppEvents.SITE_READY, { siteName: matchedConfig.name });
	}
	getActiveSite() {
		return this.activeSite;
	}
};
/** Constructed once from app/modules (composition root). */
function createSiteService() {
	return new SiteService();
}
var getAnimations = (C) => {
	const reducedMotionTargets = `:is(
            #fc2-modern-ui-host,
            #fc2-enh-settings-host,
            #fc2-enh-settings-container,
            .fc2-enh-settings-panel,
            .fc2-enh-modal-overlay,
            .fc2-enh-modal-panel,
            .fc2-action-sheet,
            .fc2-collection-grid,
            .fc2-collection-toolbar,
            .fc2-fab-container,
            .fc2-enh-toolbar,
            .fc2-enh-viewer-backdrop,
            .${C.cardRebuilt},
            .${C.processedCard}
        )`;
	return `
    /* ============================================================
       GLOBAL ANIMATIONS
       ============================================================ */

    /* Generic Fade & Slide In */
    @keyframes fc2-fade-in {
        from {
            opacity: 0;
            transform: translateY(var(--fc2-space-sm));
        }
        to {
            opacity: 1;
            transform: translateY(0);
        }
    }

    /* Master Reveal Animation for Cards */
    @keyframes fc2-card-reveal {
        0% { 
            opacity: 0; 
            transform: translate3d(0, var(--fc2-space-base), 0) scale(0.97);
            filter: blur(calc(var(--fc2-blur) / 3));
        }
        100% { 
            opacity: 1; 
            transform: translate3d(0, 0, 0) scale(1);
            filter: blur(0);
        }
    }

    @keyframes fc2-content-reveal {
        0% { 
            opacity: 0; 
            transform: translate3d(0, 0, 0);
        }
        100% { 
            opacity: 1; 
            transform: translate3d(0, 0, 0);
        }
    }

    /* Core Spinner */
    @keyframes fc2-spin {
        from { transform: rotate(0deg); }
        to { transform: rotate(360deg); }
    }

    @keyframes fc2-shimmer {
        0% { background-position: -200% 0; }
        100% { background-position: 200% 0; }
    }

    @keyframes fc2-skeleton-pulse {
        0% { opacity: 0.6; }
        50% { opacity: 1; }
        100% { opacity: 0.6; }
    }

    /* Pulse Effects */
    @keyframes fc2-pulse-sync {
        0% {
            transform: scale(0.8);
            opacity: 0.6;
        }
        50% {
            transform: scale(1.1);
            opacity: 1;
        }
        100% {
            transform: scale(0.8);
            opacity: 0.6;
        }
    }

    @keyframes fc2-pulse-once {
        0% { transform: scale(1); }
        50% {
            transform: scale(1.15);
            background: var(--fc2-primary);
            color: var(--fc2-on-primary);
        }
        100% { transform: scale(1); }
    }

    /* UI Logic Specific */
    @keyframes fc2-copy-success {
        0% { transform: scale(1); }
        50% {
            transform: scale(1.1);
            background: var(--fc2-success);
        }
        100% { transform: scale(1); }
    }

    @keyframes fc2-magnet-in {
        0% {
            transform: scale(0.3) translateY(var(--fc2-space-lg));
            opacity: 0;
            filter: blur(calc(var(--fc2-blur) / 2));
        }
        60% {
            transform: scale(1.1) translateY(calc(-1 * var(--fc2-space-xs)));
            filter: blur(0);
        }
        85% {
            transform: scale(0.95) translateY(var(--fc2-border-width));
        }
        100% {
            transform: scale(1) translateY(0);
            opacity: 1;
        }
    }

    @keyframes fc2-pop-in {
        0% {
            opacity: 0;
            transform: translate(-50%, -45%) scale(0.92);
            filter: blur(var(--fc2-blur));
        }
        70% {
            transform: translate(-50%, -51%) scale(1.02);
            filter: blur(0);
        }
        100% {
            opacity: 1;
            transform: translate(-50%, -50%) scale(1);
        }
    }

    @keyframes fc2-ripple {
        0% {
            opacity: 0.24;
            transform: translate(-50%, -50%) scale(0.72);
        }
        100% {
            opacity: 0;
            transform: translate(-50%, -50%) scale(1.32);
        }
    }

    /* Gallery & Media Animations */
    @keyframes fc2-gallery-zoom {
        0% { opacity: 0; transform: scale(0.95); }
        100% { opacity: 1; transform: scale(1); }
    }

    @keyframes fc2-slide-right-in {
        0% { opacity: 0; transform: translateX(var(--fc2-space-xl)); }
        100% { opacity: 1; transform: translateX(0); }
    }

    @keyframes fc2-slide-left-in {
        0% { opacity: 0; transform: translateX(calc(-1 * var(--fc2-space-xl))); }
        100% { opacity: 1; transform: translateX(0); }
    }

    @keyframes fc2-animate-pop {
        0% { transform: scale(1); }
        50% { transform: scale(1.25); }
        100% { transform: scale(1); }
    }

    @keyframes fc2-star-burst {
        0% { transform: scale(0); opacity: 1; }
        100% { transform: scale(2.5); opacity: 0; }
    }

    @keyframes fc2-glow-pulse {
        0% { filter: drop-shadow(0 0 var(--fc2-border-width) var(--fc2-primary)); }
        50% { filter: drop-shadow(0 0 var(--fc2-space-sm) var(--fc2-primary)); }
        100% { filter: drop-shadow(0 0 var(--fc2-border-width) var(--fc2-primary)); }
    }

    /* Toast Animations */
    @keyframes fc2-toast-in {
        from {
            opacity: 0;
            transform: translateX(var(--fc2-space-xl)) scale(0.95);
            filter: blur(var(--fc2-border-width));
        }
        to {
            opacity: 1;
            transform: translateX(0) scale(1);
            filter: blur(0);
        }
    }

    @keyframes fc2-toast-out {
        from {
            opacity: 1;
            transform: translateX(0) scale(1);
        }
        to {
            opacity: 0;
            transform: translateX(var(--fc2-space-xl)) scale(0.95);
        }
    }

    /* Helper Classes */
    .fc2-pulse-once {
        animation: fc2-pulse-once var(--fc2-motion-base) var(--fc2-ease-out);
    }
    .fc2-animate-pop {
        animation: fc2-animate-pop var(--fc2-motion-base) var(--fc2-ease-out);
    }
    .fc2-star-burst::after {
        content: '';
        position: absolute;
        inset: calc(-1 * var(--fc2-space-sm));
        border: var(--fc2-border-width) solid var(--fc2-primary);
        border-radius: var(--fc2-radius-full);
        animation: fc2-star-burst var(--fc2-motion-base) var(--fc2-ease-out) forwards;
        pointer-events: none;
    }

    @media (prefers-reduced-motion: reduce) {
        ${reducedMotionTargets},
        ${reducedMotionTargets} *,
        ${reducedMotionTargets} *::before,
        ${reducedMotionTargets} *::after {
            animation-duration: 0.01ms !important;
            animation-iteration-count: 1 !important;
            transition-duration: 0.01ms !important;
            scroll-behavior: auto !important;
        }

        ${reducedMotionTargets} :is(
            .fc2-tab-content-wrapper,
            .fc2-enh-modal-panel,
            .fc2-enh-btn,
            .fc2-btn,
            .fc2-fab-btn,
            .fc2-fab-trigger,
            .${C.resourceBtn},
            .fc2-btn-actress,
            .fc2-enh-viewer-stage img,
            .fc2-enh-viewer-stage video
        ) {
            transform: none !important;
            filter: none !important;
        }
    }
`;
};
var getBaseStyles = (C) => {
	const scrollbarScope = `#fc2-modern-ui-host,
        #fc2-enh-settings-host,
        #fc2-enh-settings-container,
        .fc2-enh-settings-panel,
        .fc2-enh-modal-overlay,
        .fc2-enh-modal-panel,
        .fc2-action-sheet,
        .fc2-fab-container,
        .fc2-enh-toolbar,
        .fc2-enh-viewer-backdrop,
        .${C.cardRebuilt},
        .${C.cardActionRow},
        .${C.infoArea}`;
	return `
    /* ============================================================
       CSS RESET & GLOBAL OVERRIDES
       ============================================================ */

    .fc2-enh-settings-panel,
    .fc2-enh-modal-overlay,
    .fc2-enh-modal-panel,
    .fc2-action-sheet,
    .${C.cardActionRow},
    .fc2-fab-container {
        all: revert;
        box-sizing: border-box;
        -webkit-tap-highlight-color: transparent !important;
        -webkit-touch-callout: none !important;
        transition: background-color var(--fc2-motion-slow) var(--fc2-ease-standard), 
                    color var(--fc2-motion-slow) var(--fc2-ease-standard), 
                    border-color var(--fc2-motion-slow) var(--fc2-ease-standard),
                    box-shadow var(--fc2-motion-slow) var(--fc2-ease-standard);
    }

    .fc2-enh-settings-panel *:not(svg):not(path):not(circle):not(rect):not(line):not(polyline):not(polygon),
    .fc2-enh-settings-panel *:not(svg):not(path):not(circle):not(rect):not(line):not(polyline):not(polygon),
    .fc2-enh-modal-panel *:not(svg):not(path):not(circle):not(rect):not(line):not(polyline):not(polygon),
    .fc2-fab-container *:not(svg):not(path):not(circle):not(rect):not(line):not(polyline):not(polygon),
    .fc2-action-sheet *:not(svg):not(path):not(circle):not(rect):not(line):not(polyline):not(polygon),
    .${C.cardActionRow} *:not(svg):not(path):not(circle):not(rect):not(line):not(polyline):not(polygon) {
        box-sizing: border-box;
        font-family: var(--fc2-font) !important;
        font-size: var(--fc2-text-base) !important;
        font-style: normal !important;
        line-height: var(--fc2-line-base) !important;
        letter-spacing: normal !important;
        text-transform: none !important;
        color: var(--fc2-text);
        text-rendering: optimizeLegibility;
        -webkit-font-smoothing: antialiased;
    }

    /* Icons Visibility Fix */
    .fc2-enh-settings-panel svg,
    .fc2-enh-settings-panel .fc2-icon,
    .fc2-fab-container svg,
    .fc2-fab-container .fc2-icon {
        display: inline-flex !important;
        align-items: center !important;
        justify-content: center !important;
        vertical-align: middle !important;
        color: inherit;
    }

    /* Headings */
    .fc2-enh-settings-panel h2,
    .fc2-enh-settings-panel h3,
    .fc2-enh-settings-panel h4 {
        margin: 0 !important;
        padding: 0 !important;
        font-weight: var(--fc2-font-semibold) !important;
    }

    .fc2-enh-settings-panel h2 { font-size: var(--fc2-text-xl) !important; line-height: var(--fc2-line-tight) !important; }
    .fc2-enh-settings-panel h3 { font-size: var(--fc2-text-lg) !important; line-height: var(--fc2-line-tight) !important; }
    .fc2-enh-settings-panel h4 { font-size: var(--fc2-text-base) !important; line-height: var(--fc2-line-base) !important; }

    /* Forms & Controls */
    .fc2-enh-settings-panel label,
    .fc2-enh-settings-panel input,
    .fc2-enh-settings-panel select,
    .fc2-enh-settings-panel button {
        font-size: var(--fc2-text-sm);
        line-height: var(--fc2-line-base) !important;
    }

    /* ============================================================
       SELECT DROPDOWN STYLES
       ============================================================ */

    .fc2-enh-settings-panel select,
    .fc2-enh-settings-panel .fc2-select {
        display: inline-block !important;
        min-height: var(--fc2-btn-md) !important;
        padding: var(--fc2-space-xs) var(--fc2-space-xl) var(--fc2-space-xs) var(--fc2-space-sm) !important;
        background-color: var(--fc2-bg) !important;
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath fill='%239494a0' d='M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z'/%3E%3C/svg%3E") !important;
        background-repeat: no-repeat !important;
        background-position: right var(--fc2-space-sm) center !important;
        background-size: var(--fc2-btn-sm) !important;
        color: var(--fc2-text) !important;
        border: var(--fc2-border-width) solid var(--fc2-border) !important;
        border-radius: var(--fc2-radius-sm) !important;
        cursor: pointer !important;
        appearance: none !important;
        -webkit-appearance: none !important;
        color-scheme: light dark !important;
        transition: all var(--fc2-motion-fast) var(--fc2-ease-standard);
    }

    .fc2-enh-settings-panel select: hover {
        border-color: var(--fc2-text-dim) !important;
        background-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
    }

    .fc2-enh-settings-panel select: focus {
        outline: none !important;
        border-color: var(--fc2-primary) !important;
        box-shadow: var(--fc2-focus-ring) !important;
    }

    .fc2-enh-settings-panel select option {
        padding: var(--fc2-space-xs) var(--fc2-space-sm) !important;
        background: var(--fc2-bg) !important;
        color: var(--fc2-text) !important;
    }

    .fc2-enh-settings-panel select option: checked,
    .fc2-enh-settings-panel select option: hover {
        background: var(--fc2-primary) !important;
        color: var(--fc2-on-primary) !important;
    }

    /* ============================================================
       SCROLLBAR & UTILITIES
       ============================================================ */

    /* Scoped Scrollbar */
    :is(${scrollbarScope})::-webkit-scrollbar,
    :is(${scrollbarScope}) *::-webkit-scrollbar {
        width: calc(var(--fc2-space-sm) - var(--fc2-space-xxs));
        height: calc(var(--fc2-space-sm) - var(--fc2-space-xxs));
    }

    :is(${scrollbarScope})::-webkit-scrollbar-track,
    :is(${scrollbarScope}) *::-webkit-scrollbar-track {
        background: transparent;
    }

    :is(${scrollbarScope})::-webkit-scrollbar-thumb,
    :is(${scrollbarScope}) *::-webkit-scrollbar-thumb {
        background: var(--fc2-scrollbar-thumb) !important;
        border-radius: var(--fc2-btn-radius);
    }

    :is(${scrollbarScope})::-webkit-scrollbar-thumb:hover,
    :is(${scrollbarScope}) *::-webkit-scrollbar-thumb:hover {
        background: var(--fc2-scrollbar-hover) !important;
    }

    /* Functional Hide Classes */
    .${C.hideNoMagnet}, 
    .${C.hideCensored}, 
    .${C.hideViewed},
    .fc2-is-hidden {
        display: none !important;
    }

    [data-enh-grid-container] > *:has(.${C.hideNoMagnet}),
    [data-enh-grid-container] > *:has(.${C.hideCensored}),
    [data-enh-grid-container] > *:has(.${C.hideViewed}) {
        display: none !important;
    }

    .fc2-is-invisible {
        visibility: hidden !important;
    }

    body.fc2-settings-open {
        overflow: hidden !important;
    }

    .fc2-ml-sm {
        margin-left: var(--fc2-space-sm) !important;
    }

    #fc2-enh-settings-host {
        position: fixed;
        inset: 0;
        z-index: var(--fc2-z-modal);
    }

    #fc2-enh-settings-container {
        position: absolute;
        inset: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        font-family: var(--fc2-font);
        padding: var(--fc2-space-md);
    }
`;
};
var getMobileStyles = (C) => `
    /* ============================================================
       MOBILE TOUCH OPTIMIZATIONS
       ============================================================ */

    @media (max-width: 768px) and (hover: none), (max-width: 768px) and (pointer: coarse) {
        /* Keep mobile interaction tuning scoped to userscript-owned UI only */
        .${C.processedCard},
        .${C.cardRebuilt},
        .${C.resourceBtn},
        .fc2-card-top-right-controls > *,
        .fc2-enh-tab-btn,
        .fc2-portal-item,
        .data-management-actions > *,
        .fc2-fab-btn,
        .fc2-fab-trigger,
        .fc2-enh-viewer-action,
        .fc2-enh-viewer-close,
        .fc2-enh-thumb-item,
        .fc2-enh-btn,
        .fc2-btn,
        .batch-btn,
        .fc2-action-sheet-item,
        .fc2-close-btn,
        .fc2-enh-settings-panel button,
        .fc2-enh-input,
        .fc2-enh-select,
        .fc2-enh-textarea {
            touch-action: manipulation;
            animation-duration: 0.2s !important;
            transition-duration: 0.2s !important;
        }

        /* Hardware Acceleration for Mobile */
        .${C.processedCard},
        .${C.cardRebuilt},
        .${C.resourceBtn},
        .fc2-fab-btn,
        .fc2-fab-trigger {
            transform: translateZ(0);
            -webkit-transform: translateZ(0);
            will-change: transform;
        }
        
        /* Disable hover effects on touch devices to prevent "sticky" hover */
        @media (hover: none) {
            .${C.resourceBtn}:hover,
            .fc2-card-top-right-controls > *:hover,
            .fc2-fab-btn:hover,
            .fc2-fab-trigger:hover,
            .${C.processedCard}:hover {
                transform: none !important;
                box-shadow: none !important;
                border-color: rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle)) !important;
            }
        }
        
        /* ============================================================
           SETTINGS PANEL MOBILE
           ============================================================ */

        .fc2-enh-settings-panel { 
            display: flex !important;
            flex-direction: column !important;
        }

        .fc2-enh-settings-body {
            flex-direction: column !important;
        }

        .fc2-enh-settings-content {
            padding: 0 !important;
        }

        .fc2-enh-tab-btn {
            display: flex;
            flex: 1 1 1180px;
            width: auto !important;
            justify-content: center;
            padding: var(--fc2-space-sm) var(--fc2-space-xs) !important;
            min-height: var(--fc2-btn-lg) !important;
            height: auto !important;
            font-size: var(--fc2-text-xs) !important;
            border-radius: var(--fc2-radius-md) !important;
            min-width: 0;
        }
        
        .fc2-enh-form-row {
            flex-direction: column !important;
            align-items: flex-start !important;
            gap: var(--fc2-space-sm) !important;
            padding: var(--fc2-space-base) 0 !important;
            border-bottom: var(--fc2-border-width) solid rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost)) !important;
        }

        /* Prevent checkboxes from splitting on mobile */
        .fc2-enh-form-row.checkbox {
            flex-direction: row !important;
            align-items: center !important;
            padding: var(--fc2-space-base) var(--fc2-space-sm) !important;
        }

        .fc2-portal-grid {
            grid-template-columns: 1fr !important;
            gap: var(--fc2-space-sm) !important;
        }

        .fc2-portal-item {
            padding: var(--fc2-space-sm) !important;
            font-size: var(--fc2-text-sm) !important;
        }

        .data-management-actions {
            flex-direction: row !important;
            flex-wrap: wrap !important;
            gap: var(--fc2-space-sm) !important;
        }

        .data-management-actions > * {
            flex: 1 1 calc(50% - var(--fc2-space-sm)) !important;
            min-width: 120px !important;
            height: var(--fc2-btn-lg) !important;
        }

        /* ============================================================
           GRID & CARD LAYOUT MOBILE
           ============================================================ */

        .${C.cardRebuilt} {
            width: 100% !important;
            max-width: 100% !important;
            margin: 0 !important;
            flex-basis: auto !important; /* Allow grid to define width */
        }
        
        /* Larger Touch Targets for Mobile */
        .fc2-card-top-right-controls { 
            top: var(--fc2-space-base) !important; 
            right: var(--fc2-space-base) !important; 
            gap: var(--fc2-space-sm) !important; 
        }

        .fc2-card-top-right-controls > * { 
            min-height: var(--fc2-btn-lg);
            min-width: var(--fc2-btn-lg);
            height: var(--fc2-btn-md) !important; 
            padding: 0 var(--fc2-space-base) !important; 
            font-size: var(--fc2-text-base) !important; 
        }

        .${C.resourceBtn} { 
            min-height: var(--fc2-btn-lg);
            height: var(--fc2-btn-lg) !important; 
            padding: 0 var(--fc2-space-md) !important; 
            font-size: var(--fc2-text-base) !important; 
        }

        .${C.infoArea} {
            padding: var(--fc2-space-base) !important;
        }

        .${C.customTitle} {
            height: auto !important;
            min-height: var(--fc2-btn-md);
            margin-bottom: var(--fc2-space-sm) !important;
        }

        .${C.cardMeta} {
            margin-bottom: var(--fc2-space-sm) !important;
        }

        .${C.cardActionRow} {
            width: 100% !important;
            justify-content: flex-start !important;
        }

        .${C.cardActionRow} {
            display: flex !important;
            flex-wrap: wrap !important;
            justify-content: flex-end !important;
            align-items: start !important;
            gap: var(--fc2-space-sm) !important;
        }

        .${C.cardActionRow} .${C.resourceLinksContainer} {
            display: flex !important;
            flex-wrap: wrap !important;
            justify-content: flex-end !important;
            gap: var(--fc2-space-sm) !important;
            width: 100% !important;
            max-width: 100% !important;
            min-width: 0 !important;
            margin-left: 0 !important;
            padding: 0 !important;
            height: auto !important;
            min-height: var(--fc2-btn-lg) !important;
            overflow: visible !important;
            background: transparent !important;
            border: none !important;
            box-shadow: none !important;
            backdrop-filter: none !important;
            -webkit-backdrop-filter: none !important;
        }

        .${C.cardActionRow} .${C.resourceLinksContainer} > * {
            flex: 0 0 auto !important;
            width: var(--fc2-btn-lg) !important;
            min-width: var(--fc2-btn-lg) !important;
            max-width: var(--fc2-btn-lg) !important;
            height: var(--fc2-btn-lg) !important;
            min-height: var(--fc2-btn-lg) !important;
            padding: 0 !important;
            margin: 0 !important;
            border-radius: var(--fc2-radius-md) !important;
            background: var(--fc2-surface-item) !important;
            border: var(--fc2-border-width) solid var(--fc2-border) !important;
            font-size: var(--fc2-text-lg) !important;
            justify-content: center !important;
            box-shadow: inset 0 var(--fc2-border-width) 0 rgba(var(--fc2-primary-rgb), var(--fc2-opacity-ghost)) !important;
        }

        .${C.cardActionRow} .${C.resourceLinksContainer} .${C.fc2IdBadge} {
            width: auto !important;
            min-width: 0 !important;
            max-width: 100% !important;
            padding: 0 var(--fc2-space-md) !important;
            font-size: var(--fc2-text-sm) !important;
        }

        .${C.cardActionRow} .${C.resourceLinksContainer} .fc2-icon {
            font-size: var(--fc2-text-xl) !important;
            width: 1em;
            height: 1em;
        }

        .fc2-card-action-stash,
        .fc2-card-overflow-actions[hidden] {
            display: none !important;
            width: 0 !important;
            min-width: 0 !important;
            padding: 0 !important;
            margin: 0 !important;
            border: none !important;
            box-shadow: none !important;
        }

        .fc2-btn-actress { 
            width: 90% !important; 
            margin: var(--fc2-space-sm) auto !important; 
            min-height: var(--fc2-btn-lg) !important;
            padding: 0 var(--fc2-space-md) !important; 
            font-size: var(--fc2-text-base) !important; 
        }

        .fc2-fab-trigger { 
            width: var(--fc2-btn-lg); 
            height: var(--fc2-btn-lg); 
            font-size: var(--fc2-text-xl) !important;
        }

        .fc2-fab-btn { 
            width: var(--fc2-btn-md); 
            height: var(--fc2-btn-md); 
            font-size: var(--fc2-text-xl) !important; 
        }
        
        /* ============================================================
           TOOLBAR & MODAL MOBILE
           ============================================================ */

        .fc2-enh-toolbar { 
            display: block !important;
            height: auto !important;
            min-height: 0 !important;
            margin: var(--fc2-space-base) 0 !important; 
            padding: 0 !important;
            border-radius: var(--fc2-radius-md) !important;
            overflow: hidden !important;
        }

        .fc2-enh-toolbar .fc2-info-area { 
            display: grid !important;
            grid-template-columns: minmax(0, 1fr) auto !important;
            gap: var(--fc2-space-sm) !important;
            width: 100% !important;
            min-width: 0 !important;
            height: auto !important;
            padding: var(--fc2-space-sm) !important;
            align-items: center !important;
        }

        .fc2-enh-toolbar .fc2-card-top-right-controls,
        .fc2-enh-toolbar .fc2-resource-links-container { 
            display: flex !important;
            flex-direction: row !important;
            flex-wrap: nowrap !important;
            justify-content: flex-start !important;
            align-items: center !important;
            margin: 0 !important;
            min-width: 0 !important;
            width: auto !important;
            max-width: 100% !important;
            height: auto !important;
            min-height: var(--fc2-btn-lg) !important;
        }

        .fc2-enh-toolbar .fc2-toolbar-section-controls {
            grid-column: 1 !important;
            justify-self: start !important;
        }

        .fc2-enh-toolbar .fc2-toolbar-section-links {
            grid-column: 2 !important;
            justify-self: end !important;
        }

        .fc2-enh-toolbar .fc2-resource-links-container { 
            gap: 0 !important; 
            overflow: hidden !important;
        }

        .fc2-enh-toolbar .fc2-btn-actress {
            grid-column: 1 / -1 !important;
            justify-self: stretch !important;
            display: inline-flex !important;
            width: 100% !important;
            max-width: none !important;
            min-height: var(--fc2-btn-lg) !important;
            padding: 0 var(--fc2-space-md) !important;
        }

        .fc2-enh-toolbar .fc2-card-top-right-controls > *,
        .fc2-enh-toolbar .fc2-resource-links-container > * {
            flex: 0 0 auto !important;
            justify-content: center !important;
            min-width: var(--fc2-btn-lg) !important;
            height: var(--fc2-btn-lg) !important;
            min-height: var(--fc2-btn-lg) !important;
        }

        .fc2-enh-toolbar .fc2-card-top-right-controls .${C.fc2IdBadge} {
            flex-basis: auto !important;
            min-width: 0 !important;
        }

        .fc2-enh-toolbar .fc2-card-top-right-controls .fc2-icon,
        .fc2-enh-toolbar .fc2-resource-links-container .fc2-icon {
            font-size: var(--fc2-text-xl) !important;
            width: 1em;
            height: 1em;
        }

        .fc2-enh-viewer-nav { 
            display: none !important; /* Hide arrows on mobile, rely on swipe */
        }
        
        .fc2-enh-viewer-thumbs {
            bottom: var(--fc2-space-lg) !important;
            width: 95% !important;
            height: var(--fc2-btn-lg) !important;
            gap: var(--fc2-space-xs) !important;
        }

        .fc2-enh-thumb-item {
            width: var(--fc2-btn-lg) !important;
            height: var(--fc2-btn-lg) !important;
        }

        .fc2-enh-viewer-actions {
            top: var(--fc2-btn-sm) !important;
            padding: var(--fc2-space-xs) !important;
        }

        .fc2-enh-viewer-action {
            width: var(--fc2-btn-lg) !important;
            height: var(--fc2-btn-lg) !important;
        }

        .fc2-enh-viewer-close { 
            top: var(--fc2-space-sm) !important; 
            right: var(--fc2-space-sm) !important; 
            width: var(--fc2-btn-lg) !important; 
            height: var(--fc2-btn-lg) !important; 
            background: rgba(var(--fc2-black-rgb), var(--fc2-opacity-muted)) !important;
            border-radius: var(--fc2-radius-full) !important;
            backdrop-filter: blur(var(--fc2-blur)) !important;
        }

        /* Keep vertical page pan while reserving horizontal swipes for gallery navigation */
        .fc2-enh-viewer-stage {
            touch-action: pan-y;
            overscroll-behavior: contain;
            user-select: none;
            -webkit-user-select: none;
        }

        .fc2-enh-viewer-backdrop {
            overscroll-behavior: contain;
        }

        /* Prevent iOS auto-zoom when focusing script-owned form controls */
        .fc2-enh-settings-panel input,
        .fc2-enh-settings-panel select,
        .fc2-enh-settings-panel textarea,
        .fc2-enh-input,
        .fc2-enh-select,
        .fc2-enh-textarea {
            font-size: 16px !important;
        }
        
        .fc2-enh-modal-panel {
            width: 95% !important;
            max-width: 95% !important;
            max-height: 90vh !important;
        }

        /* Improved Grid Responsiveness */
        .preview-hero-section {
            margin-bottom: var(--fc2-space-md) !important;
        }

        .preview-hero-card {
            border-radius: var(--fc2-radius-md) !important;
            overflow: hidden !important;
        }

        .${C.extraPreviewGrid} {
            grid-template-columns: repeat(2, 1fr) !important;
            gap: var(--fc2-space-sm) !important;
        }

        .preview-item {
            height: 120px !important;
            border-radius: var(--fc2-radius-sm) !important;
        }
        
        /* FAB Container - safely above bottom bar by default, but allow JS to override */
        .fc2-fab-container {
            bottom: calc(var(--fc2-space-2xl) + var(--fc2-space-2xl)); 
            right: var(--fc2-space-lg);
        }

        /* Toasts mobile positioning with safe area */
        .fc2-toast-container {
            top: calc(var(--fc2-space-sm) + env(safe-area-inset-top, 0px)) !important;
            right: var(--fc2-space-sm) !important;
            left: var(--fc2-space-sm) !important; /* Full width on mobile looks better */
        }

        .fc2-toast-item {
            min-width: 0 !important;
            width: 100% !important;
        }

        /* Better touch feedback */
        .fc2-fab-trigger,
        .fc2-fab-btn,
        .fc2-enh-viewer-action,
        .fc2-enh-thumb-item {
            -webkit-tap-highlight-color: rgba(var(--fc2-primary-rgb), 0.2) !important;
        }
    }
`;
var SITE_TONE_CLASS_PREFIX = "fc2-site-";
var SITE_CONFIGS = {
	default: {
		hostBgRgb: "16, 18, 22",
		cardRgb: "31, 34, 41",
		cardAltRgb: "24, 28, 35",
		toolbarRgb: "24, 27, 34",
		accentRgb: "255, 255, 255",
		cardAlpha: 1,
		borderAlpha: .08
	},
	fc2db: {
		hostBgRgb: "245, 246, 247",
		cardRgb: "255, 255, 255",
		cardAltRgb: "249, 250, 251",
		toolbarRgb: "255, 255, 255",
		accentRgb: "230, 230, 232",
		cardAlpha: 1,
		borderAlpha: 1,
		borderRadius: "8px",
		shadow: "0 1px 3px rgba(0, 0, 0, 0.06)",
		lightCardRgb: "255, 255, 255",
		lightCardAltRgb: "253, 248, 249"
	},
	fd2ppv: {
		hostBgRgb: "18, 18, 18",
		cardRgb: "30, 30, 30",
		cardAltRgb: "42, 42, 42",
		toolbarRgb: "26, 26, 26",
		accentRgb: "51, 51, 51",
		cardAlpha: 1,
		borderAlpha: 1,
		borderRadius: "8px",
		shadow: "inset 0 -12px 24px rgba(0, 0, 0, 0.25)",
		lightCardRgb: "255, 255, 255",
		lightCardAltRgb: "245, 246, 247"
	},
	javdb: {
		hostBgRgb: "23, 24, 28",
		cardRgb: "10, 10, 10",
		cardAltRgb: "18, 18, 18",
		toolbarRgb: "34, 34, 34",
		accentRgb: "51, 51, 51",
		cardAlpha: 1,
		borderAlpha: 1,
		borderRadius: "6px",
		shadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
		lightCardRgb: "255, 255, 255",
		lightCardAltRgb: "245, 246, 247"
	},
	missav: {
		hostBgRgb: "9, 8, 18",
		cardRgb: "17, 21, 32",
		cardAltRgb: "28, 32, 41",
		toolbarRgb: "36, 41, 51",
		accentRgb: "76, 86, 106",
		cardAlpha: .12,
		cardAltAlpha: .08,
		borderAlpha: .25,
		borderRadius: "8px",
		shadow: "0 2px 12px rgba(0, 0, 0, 0.4)",
		lightCardRgb: "255, 255, 255",
		lightCardAltRgb: "249, 250, 251"
	},
	supjav: {
		hostBgRgb: "36, 36, 36",
		cardRgb: "24, 24, 24",
		cardAltRgb: "27, 27, 27",
		toolbarRgb: "27, 27, 27",
		accentRgb: "51, 51, 51",
		cardAlpha: .15,
		cardAltAlpha: .1,
		borderAlpha: .2,
		borderRadius: "6px",
		shadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
		lightCardRgb: "255, 255, 255",
		lightCardAltRgb: "252, 245, 245"
	},
	javfc2: {
		hostBgRgb: "19, 19, 19",
		cardRgb: "35, 35, 35",
		cardAltRgb: "27, 27, 27",
		toolbarRgb: "15, 15, 15",
		accentRgb: "33, 33, 33",
		cardAlpha: .12,
		cardAltAlpha: .08,
		borderAlpha: .2,
		borderRadius: "5px",
		shadow: "1px 1px 4px rgba(0, 0, 0, 0.3)"
	},
	fc2cmadb: {
		hostBgRgb: "255, 255, 255",
		cardRgb: "255, 255, 255",
		cardAltRgb: "250, 250, 250",
		toolbarRgb: "255, 255, 255",
		accentRgb: "26, 26, 46",
		cardAlpha: 1,
		borderAlpha: 0,
		borderRadius: "8px",
		shadow: "0 2px 8px rgba(0, 0, 0, 0.08)",
		lightCardRgb: "255, 255, 255",
		lightCardAltRgb: "248, 248, 252"
	}
};
var resolveSiteToneKey = (hostname) => {
	if (hostname.includes("fc2db.net")) return "fc2db";
	if (hostname.includes("fd2ppv.cc")) return "fd2ppv";
	if (hostname.includes("javdb")) return "javdb";
	if (hostname.includes("missav")) return "missav";
	if (hostname.includes("supjav.com")) return "supjav";
	if (hostname.includes("javfc2.xyz")) return "javfc2";
	if (hostname.includes("fc2cmadb.com")) return "fc2cmadb";
	return "default";
};
var resolveSiteToneClass = (hostname) => {
	return `${SITE_TONE_CLASS_PREFIX}${resolveSiteToneKey(hostname)}`;
};
var generateCommonVariables = () => `
    :root {
        --fc2-host-bg-rgb: 16, 18, 22;
        --fc2-host-bg-luma: 21;

        --fc2-site-card-rgb: 31, 34, 41;
        --fc2-site-card-alt-rgb: 24, 28, 35;
        --fc2-site-toolbar-rgb: 24, 27, 34;
        --fc2-site-accent-rgb: 255, 255, 255;

        --fc2-site-card-alpha: 1;
        --fc2-site-card-alt-alpha: 1;
        --fc2-site-toolbar-alpha: 1;
        --fc2-site-border-alpha: 0.08;

        --fc2-bg: rgb(var(--fc2-host-bg-rgb));
        --fc2-surface: rgba(var(--fc2-site-card-rgb), var(--fc2-site-card-alpha));
        --fc2-surface-item: rgba(var(--fc2-site-card-alt-rgb), var(--fc2-site-card-alt-alpha));
        --fc2-surface-float: rgba(var(--fc2-site-toolbar-rgb), var(--fc2-site-toolbar-alpha));
        --fc2-border: rgba(var(--fc2-site-accent-rgb), var(--fc2-site-border-alpha));
        --fc2-accent-grad: linear-gradient(
            145deg,
            rgba(var(--fc2-site-accent-rgb), var(--fc2-opacity-subtle)),
            rgba(var(--fc2-site-card-rgb), var(--fc2-opacity-ghost))
        );
    }
`;
var generateSiteVariables = (key, config) => `
    :root.${SITE_TONE_CLASS_PREFIX}${key} {
        --fc2-host-bg-rgb: ${config.hostBgRgb};
        --fc2-site-card-rgb: ${config.cardRgb};
        --fc2-site-card-alt-rgb: ${config.cardAltRgb};
        --fc2-site-toolbar-rgb: ${config.toolbarRgb};
        --fc2-site-accent-rgb: ${config.accentRgb};
        --fc2-site-card-alpha: ${config.cardAlpha};
        ${config.cardAltAlpha !== void 0 ? `--fc2-site-card-alt-alpha: ${config.cardAltAlpha};` : ""}
        ${config.toolbarAlpha !== void 0 ? `--fc2-site-toolbar-alpha: ${config.toolbarAlpha};` : ""}
        --fc2-site-border-alpha: ${config.borderAlpha};
    }
`;
var generateSiteCardStyles = (key, config, C) => {
	const shadow = config.shadow || "0 1px 3px rgba(0, 0, 0, 0.12)";
	const borderRadius = config.borderRadius || "";
	return `
    :root.${SITE_TONE_CLASS_PREFIX}${key} .${C.cardRebuilt},
    :root.${SITE_TONE_CLASS_PREFIX}${key} .${C.processedCard}:not(.fc2-is-minimal) {
        background-image: none !important;
        background-color: rgba(var(--fc2-site-card-rgb), var(--fc2-site-card-alpha));
        box-shadow: ${shadow};
        border: 1px solid rgba(var(--fc2-site-accent-rgb), var(--fc2-site-border-alpha));
        ${borderRadius ? `border-radius: ${borderRadius};` : ""}
    }`;
};
var generateLightThemeOverrides = (key, config) => {
	if (!config.lightCardRgb || !config.lightCardAltRgb) return "";
	return `
    :root.fc2-light-theme.${SITE_TONE_CLASS_PREFIX}${key} {
        --fc2-site-card-rgb: ${config.lightCardRgb};
        --fc2-site-card-alt-rgb: ${config.lightCardAltRgb};
    }`;
};
var generateCommonCardFusion = (C) => `
    /* Default card fusion */
    .${C.cardRebuilt},
    .${C.processedCard}:not(.fc2-is-minimal) {
        background-image: none;
        background-color: rgba(var(--fc2-site-card-rgb), var(--fc2-site-card-alpha));
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
        border: 1px solid rgba(var(--fc2-site-accent-rgb), var(--fc2-site-border-alpha));
    }

    .${C.processedCard}.fc2-is-detail .${C.infoArea} {
        background-image: linear-gradient(
            180deg,
            rgba(var(--fc2-site-toolbar-rgb), 0.04),
            rgba(var(--fc2-site-card-alt-rgb), 0.02)
        );
        border-color: rgba(var(--fc2-site-accent-rgb), var(--fc2-site-border-alpha));
    }

    .${C.videoPreviewContainer} {
        background: rgba(var(--fc2-host-bg-rgb), 0.94);
    }

    .fc2-enh-toolbar {
        background-color: rgba(var(--fc2-site-toolbar-rgb), 0.92);
        border: 1px solid rgba(var(--fc2-site-accent-rgb), 0.06);
        border-radius: var(--fc2-radius-md);
    }
`;
var generateLightThemeBase = () => `
    /* Light Theme (global detection) */
    :root.fc2-light-theme {
        --fc2-host-bg-rgb: 246, 247, 249;

        --fc2-site-card-rgb: 251, 251, 253;
        --fc2-site-card-alt-rgb: 245, 247, 250;
        --fc2-site-toolbar-rgb: 255, 255, 255;
        --fc2-site-accent-rgb: 17, 17, 17;

        --fc2-site-card-alpha: 1;
        --fc2-site-card-alt-alpha: 1;
        --fc2-site-toolbar-alpha: 1;
        --fc2-site-border-alpha: 0.10;
    }
`;
var FC2DB_EXTRA = (C) => `
    /* FC2DB: poster ratio for cover images */
    :root.${SITE_TONE_CLASS_PREFIX}fc2db .${C.videoPreviewContainer} {
        aspect-ratio: 71 / 100;
    }

    :root.${SITE_TONE_CLASS_PREFIX}fc2db .${C.videoPreviewContainer} img {
        object-fit: cover !important;
    }
`;
var JAVDB_EXTRA = (C) => `
    /* JavDB: list covers use poster ratio */
    :root.${SITE_TONE_CLASS_PREFIX}javdb .${C.videoPreviewContainer} {
        aspect-ratio: var(--fc2-javdb-cover-ratio, 2 / 3);
    }

    :root.${SITE_TONE_CLASS_PREFIX}javdb .${C.videoPreviewContainer} img {
        object-fit: contain !important;
    }
`;
var JAVFC2_EXTRA = (C) => `
    /* JavFC2: Carousel & layout fixes */
    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .owl-carousel,
    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .owl-stage-outer {
        overflow: hidden !important;
    }

    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .owl-item {
        overflow: visible !important;
    }

    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .owl-item .${C.infoArea} {
        background-color: rgba(var(--fc2-site-card-alt-rgb), 0.96) !important;
    }

    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .owl-item .${C.customTitle} {
        font-size: var(--fc2-text-xs) !important;
        max-height: 2.8em !important;
        margin-bottom: var(--fc2-space-sm) !important;
        display: -webkit-box !important;
        -webkit-line-clamp: 2 !important;
        -webkit-box-orient: vertical !important;
        overflow: hidden !important;
    }

    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .owl-item .${C.cardActionRow} {
        gap: var(--fc2-space-xs) !important;
        flex-wrap: wrap !important;
        justify-content: center !important;
    }

    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .owl-item .en-pill-btn {
        padding: 2px var(--fc2-space-sm) !important;
        font-size: var(--fc2-text-xs) !important;
        min-width: var(--fc2-btn-sm) !important;
    }

    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .owl-item .${C.cardMeta} {
        display: none !important;
    }

    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .${C.processedCard} {
        box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.3) !important;
        height: auto !important;
    }

    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .${C.videoPreviewContainer} {
        aspect-ratio: 3 / 4 !important;
    }

    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .${C.videoPreviewContainer} img {
        object-fit: cover !important;
    }

    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .${C.infoArea} {
        padding: var(--fc2-space-sm) !important;
    }

    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .${C.customTitle} {
        font-size: var(--fc2-text-xs) !important;
        line-height: 1.4 !important;
        height: 2.8em !important;
    }

    /* JavFC2: strip double borders on replaced cards */
    :root.${SITE_TONE_CLASS_PREFIX}javfc2 .${C.cardRebuilt} {
        background: none !important;
        border: none !important;
        box-shadow: none !important;
        padding: 0 !important;
    }

    /* Mobile and narrow screen optimizations */
    @media (max-width: 640px) {
        :root.${SITE_TONE_CLASS_PREFIX}javfc2 .owl-item .${C.infoArea} {
            min-height: 90px !important;
            padding: var(--fc2-space-xs) !important;
        }
        :root.${SITE_TONE_CLASS_PREFIX}javfc2 .owl-item .${C.customTitle} {
            font-size: var(--fc2-text-xs) !important;
            height: 2.6em !important;
            margin-bottom: var(--fc2-space-xs) !important;
        }
        :root.${SITE_TONE_CLASS_PREFIX}javfc2 .owl-item .en-pill-btn {
            font-size: 9px !important;
            padding: 1px 4px !important;
        }
    }
`;
var FC2CMADB_EXTRA = (C) => `
    /* DaisyUI v5 token passthrough */
    @supports (color: var(--color-base-100)) {
        :root.${SITE_TONE_CLASS_PREFIX}fc2cmadb .${C.cardRebuilt},
        :root.${SITE_TONE_CLASS_PREFIX}fc2cmadb .${C.processedCard}:not(.fc2-is-minimal) {
            background-color: var(--color-base-100);
        }

        :root.${SITE_TONE_CLASS_PREFIX}fc2cmadb .${C.infoArea} {
            background: rgba(128, 128, 128, 0.06);
            border-top-color: rgba(128, 128, 128, 0.15);
        }

        :root.${SITE_TONE_CLASS_PREFIX}fc2cmadb .${C.resourceLinksContainer} {
            background: rgba(128, 128, 128, 0.06);
            border-color: rgba(128, 128, 128, 0.12);
        }

        :root.${SITE_TONE_CLASS_PREFIX}fc2cmadb .${C.resourceLinksContainer} > *:not(:last-child) {
            border-right-color: rgba(128, 128, 128, 0.15) !important;
        }

        :root.${SITE_TONE_CLASS_PREFIX}fc2cmadb .fc2-card-meta-row .${C.resourceBtn} {
            background: rgba(128, 128, 128, 0.10);
            border-color: rgba(128, 128, 128, 0.15);
        }

        :root.${SITE_TONE_CLASS_PREFIX}fc2cmadb .fc2-card-action-row .${C.resourceLinksContainer} > *:hover {
            background: rgba(128, 128, 128, 0.10) !important;
        }
    }
`;
var getSiteFusionStyles = (C, hostname) => {
	const siteKey = resolveSiteToneKey(hostname || (typeof location !== "undefined" ? location.hostname : ""));
	const config = SITE_CONFIGS[siteKey];
	let css = generateCommonVariables();
	css += generateSiteVariables(siteKey, config);
	css += generateSiteCardStyles(siteKey, config, C);
	if (siteKey === "fc2db") css += FC2DB_EXTRA(C);
	else if (siteKey === "javdb") css += JAVDB_EXTRA(C);
	else if (siteKey === "javfc2") css += JAVFC2_EXTRA(C);
	else if (siteKey === "fc2cmadb") css += FC2CMADB_EXTRA(C);
	css += generateLightThemeBase();
	css += generateLightThemeOverrides(siteKey, config);
	css += generateCommonCardFusion(C);
	return css;
};
var tokens = `
    /* ============================================================
       DESIGN TOKENS — Unified Design System
       ============================================================ */

    :root, :host {
        /* ── Colors (Semantic) ── */
        --fc2-bg: #0a0a0c;
        --fc2-surface: rgba(22, 22, 26, 0.8);
        --fc2-surface-float: rgba(28, 28, 34, 0.92);
        --fc2-surface-low: rgba(255, 255, 255, 0.015);
        --fc2-surface-lowest: rgba(255, 255, 255, 0.01);
        --fc2-surface-item: rgba(255, 255, 255, 0.03);
        --fc2-text: #f4f4f7;
        --fc2-text-dim: #9494a0;
        --fc2-text-muted: #62626e;
        --fc2-text-bright: #ffffff;
        --fc2-text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
        --fc2-border: rgba(255, 255, 255, 0.08);
        --fc2-primary: #ffffff;
        --fc2-on-primary: #000000;
        --fc2-success: #34d399;
        --fc2-danger: #f87171;
        --fc2-warn: #fab387;
        --fc2-info: #89b4fa;
        --fc2-accent: #e4e4e7;

        /* ── Semantic RGB (for rgba() usage) ── */
        --fc2-black-rgb: 0, 0, 0;
        --fc2-primary-rgb: 255, 255, 255;
        --fc2-success-rgb: 52, 211, 153;
        --fc2-danger-rgb: 248, 113, 113;
        --fc2-warn-rgb: 250, 179, 135;
        --fc2-info-rgb: 137, 180, 250;

        /* ── Opacity Scale (6 levels) ── */
        --fc2-opacity-ghost: 0.05;
        --fc2-opacity-subtle: 0.1;
        --fc2-opacity-muted: 0.25;
        --fc2-opacity-dim: 0.5;
        --fc2-opacity-strong: 0.85;
        --fc2-opacity-full: 1;

        /* ── Spacing (8px grid, 8 levels) ── */
        --fc2-space-xxs: 2px;
        --fc2-space-xs: 4px;
        --fc2-space-sm: 8px;
        --fc2-space-base: 12px;
        --fc2-space-md: 16px;
        --fc2-space-lg: 24px;
        --fc2-space-xl: 32px;
        --fc2-space-2xl: 40px;

        /* ── Typography (5 sizes, 4 weights, 3 line-heights) ── */
        --fc2-text-xs: 12px;
        --fc2-text-sm: 13px;
        --fc2-text-base: 14px;
        --fc2-text-lg: 16px;
        --fc2-text-xl: 20px;
        --fc2-font-medium: 500;
        --fc2-font-semibold: 600;
        --fc2-font-bold: 700;
        --fc2-line-tight: 1.25;
        --fc2-line-base: 1.5;
        --fc2-letter-tight: 0.5px;
        --fc2-font: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
        --fc2-font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Fira Code', monospace;

        /* ── Border Radius (4 levels + full) ── */
        --fc2-radius-sm: 6px;
        --fc2-radius-md: 12px;
        --fc2-radius-lg: 16px;
        --fc2-radius-xl: 20px;
        --fc2-radius-full: 9999px;

        /* ── Shadows (3 levels) ── */
        --fc2-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
        --fc2-shadow-md: 0 4px 8px rgba(0, 0, 0, 0.25);
        --fc2-shadow-lg: 0 6px 16px rgba(0, 0, 0, 0.3);
        --fc2-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
        --fc2-focus-ring: 0 0 0 calc(var(--fc2-space-xxs) + var(--fc2-border-width)) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));

        /* ── Motion (3 speeds, 3 curves) ── */
        --fc2-motion-fast: 0.18s;
        --fc2-motion-base: 0.24s;
        --fc2-motion-slow: 0.38s;
        --fc2-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
        --fc2-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
        --fc2-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

        /* ── Z-Index (5 levels) ── */
        --fc2-z-dropdown: 1000;
        --fc2-z-tooltip: 100000;
        --fc2-z-fab: 2147483620;
        --fc2-z-modal: 2147483600;
        --fc2-z-toast: 2147483647;

        /* ── Buttons (4 sizes) ── */
        --fc2-btn-sm: 32px;
        --fc2-btn-md: 36px;
        --fc2-btn-lg: 44px;
        --fc2-btn-radius: var(--fc2-radius-md);

        /* ── Stroke & Effects ── */
        --fc2-border-width: 1px;
        --fc2-blur: 12px;
        --fc2-glass-saturate: 180%;

        /* ── Gradients ── */
        --fc2-accent-grad: linear-gradient(135deg, #3f3f46, #18181b);

        /* ── Scrollbar ── */
        --fc2-scrollbar-thumb: rgba(255, 255, 255, 0.08);
        --fc2-scrollbar-hover: rgba(255, 255, 255, 0.15);

        /* ── Glass Effect ── */
        --fc2-liquid-bg: #0a0a0c;
        --fc2-liquid-border: 1px solid rgba(255, 255, 255, 0.08);
        --fc2-rim-light: inset 1px 1px 0px 0px rgba(255, 255, 255, 0.12),
                         inset -1px -1px 0px 0px rgba(0, 0, 0, 0.3);
        --fc2-liquid-iridescent: linear-gradient(135deg,
                                    rgba(255, 255, 255, 0.1) 0%,
                                    rgba(180, 200, 255, 0.05) 30%,
                                    rgba(255, 180, 255, 0.04) 70%,
                                    rgba(255, 255, 255, 0.1) 100%);
        --fc2-glass-noise: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");

        /* ── Extended Borders ── */
        --fc2-border-strong: rgba(255, 255, 255, 0.2);
    }

    /* ============================================================
       LIGHT THEME OVERRIDES
       ============================================================ */

    :root.fc2-light-theme {
        --fc2-bg: #f8f9fa;
        --fc2-surface: rgba(255, 255, 255, 0.8);
        --fc2-surface-float: rgba(255, 255, 255, 0.92);
        --fc2-surface-low: rgba(0, 0, 0, 0.02);
        --fc2-surface-lowest: rgba(0, 0, 0, 0.01);
        --fc2-surface-item: rgba(0, 0, 0, 0.03);
        --fc2-text: #1a1a1a;
        --fc2-text-dim: #52525b;
        --fc2-text-muted: #a1a1aa;
        --fc2-text-bright: var(--fc2-text);
        --fc2-text-shadow: none;
        --fc2-border: rgba(0, 0, 0, 0.08);
        --fc2-primary: #111111;
        --fc2-on-primary: #ffffff;
        --fc2-success: #16a34a;
        --fc2-danger: #dc2626;
        --fc2-warn: #d97706;
        --fc2-info: #2563eb;
        --fc2-accent: #3f3f46;

        --fc2-primary-rgb: 17, 17, 17;
        --fc2-success-rgb: 22, 163, 74;
        --fc2-danger-rgb: 220, 38, 38;
        --fc2-warn-rgb: 217, 119, 6;
        --fc2-info-rgb: 37, 99, 235;

        --fc2-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
        --fc2-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
        --fc2-shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.12);
        --fc2-shadow: 0 12px 32px rgba(0, 0, 0, 0.1);
        --fc2-focus-ring: 0 0 0 calc(var(--fc2-space-xxs) + var(--fc2-border-width)) rgba(var(--fc2-primary-rgb), var(--fc2-opacity-subtle));

        --fc2-accent-grad: linear-gradient(135deg, #e4e4e7, #f4f4f5);

        --fc2-scrollbar-thumb: rgba(0, 0, 0, 0.15);
        --fc2-scrollbar-hover: rgba(0, 0, 0, 0.25);

        --fc2-liquid-bg: #f0f0f2;
        --fc2-liquid-border: 1px solid rgba(0, 0, 0, 0.08);
        --fc2-rim-light: inset 1px 1px 0px 0px rgba(255, 255, 255, 0.8),
                         inset -1px -1px 0px 0px rgba(0, 0, 0, 0.05);
        --fc2-liquid-iridescent: linear-gradient(135deg,
                                    rgba(0, 0, 0, 0.03) 0%,
                                    rgba(0, 50, 100, 0.02) 30%,
                                    rgba(100, 0, 100, 0.02) 70%,
                                    rgba(0, 0, 0, 0.03) 100%);
        --fc2-border-strong: rgba(0, 0, 0, 0.2);
    }
`;
/**
* Styles Entry Point
*/
var cachedCss = null;
var cachedOptions = null;
var clearStyleCache = () => {
	cachedCss = null;
	cachedOptions = null;
};
var getPerformanceFix = (hostname) => {
	if (!(hostname.includes("missav") || hostname.includes("supjav") || hostname.includes("javdb") || hostname.includes("fc2cmadb"))) return "";
	return `
        .${Config.CLASSES.processedCard}:nth-child(n+51) {
            content-visibility: auto;
            contain-intrinsic-size: 320px 280px;
        }
    `;
};
var getSiteSpecificFix = (hostname) => {
	if (!hostname.includes("fd2ppv")) return "";
	return `
        .artist-card.fc2-card-rebuilt,
        .work-card.fc2-card-rebuilt,
        .work-list > div,
        .artist-list > div {
            overflow: visible !important;
        }
    `;
};
var getConsolidatedCss = (options) => {
	const opts = {
		minify: true,
		includePerformanceFix: true,
		includeSiteFix: true,
		...options
	};
	if (cachedCss && cachedOptions && JSON.stringify(cachedOptions) === JSON.stringify(opts)) return cachedCss;
	const C = Config.CLASSES;
	const hostname = opts.hostname || (typeof location !== "undefined" ? location.hostname : "");
	const rawCss = [
		tokens,
		getSiteFusionStyles(C, hostname),
		getAnimations(C),
		getBaseStyles(C),
		getComponentStyles(C),
		opts.includePerformanceFix ? getPerformanceFix(hostname) : "",
		opts.includeSiteFix ? getSiteSpecificFix(hostname) : "",
		getMobileStyles(C)
	].filter(Boolean).join("\n");
	const finalCss = opts.minify ? minifyCss(rawCss) : rawCss;
	cachedCss = finalCss;
	cachedOptions = opts;
	return finalCss;
};
var log$28 = Logger.scope("Style");
var StyleService = class {
	themeObserver = null;
	injectedStyle = null;
	domReadyHandler = null;
	preferredSchemeQuery = null;
	preferredSchemeHandler = null;
	currentSiteToneClass = null;
	pendingThemeDetection = false;
	dataThemeObserver = null;
	onInit() {
		log$28.debug("Injecting global styles");
		this.injectCss();
		this.initThemeDetection();
	}
	onCleanup() {
		this.dataThemeObserver?.disconnect();
		this.dataThemeObserver = null;
		this.themeObserver?.disconnect();
		this.themeObserver = null;
		if (this.domReadyHandler) {
			document.removeEventListener("DOMContentLoaded", this.domReadyHandler);
			this.domReadyHandler = null;
		}
		if (this.preferredSchemeQuery && this.preferredSchemeHandler) {
			if (typeof this.preferredSchemeQuery.removeEventListener === "function") this.preferredSchemeQuery.removeEventListener("change", this.preferredSchemeHandler);
			else if (typeof this.preferredSchemeQuery.removeListener === "function") this.preferredSchemeQuery.removeListener(this.preferredSchemeHandler);
		}
		this.preferredSchemeQuery = null;
		this.preferredSchemeHandler = null;
		if (this.injectedStyle?.isConnected) this.injectedStyle.remove();
		this.injectedStyle = null;
		const root = document.documentElement;
		if (this.currentSiteToneClass && root.classList.contains(this.currentSiteToneClass)) root.classList.remove(this.currentSiteToneClass);
		this.currentSiteToneClass = null;
		root.style.removeProperty("--fc2-host-bg-rgb");
		root.style.removeProperty("--fc2-host-bg-luma");
		clearStyleCache();
	}
	parseRgbColor(color) {
		const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
		if (!match) return null;
		const r = Number(match[1]);
		const g = Number(match[2]);
		const b = Number(match[3]);
		if ([
			r,
			g,
			b
		].some((value) => Number.isNaN(value))) return null;
		return {
			r,
			g,
			b
		};
	}
	resolveHostBackgroundColor() {
		const html = document.documentElement;
		if (html?.classList.contains("dark") || html?.getAttribute("data-theme") === "dark" || html?.getAttribute("data-mode") === "dark" || document.body?.classList.contains("dark")) return "rgb(17, 24, 39)";
		if (window.location.hostname.includes("missav") || window.location.hostname.includes("javfc2.xyz") || document.querySelector("[class*=\"bg-nord\"]")) return "rgb(18, 18, 18)";
		const candidates = [
			document.body,
			document.querySelector("#app"),
			document.querySelector("#__nuxt"),
			document.querySelector(".app"),
			document.querySelector(".wrapper"),
			document.querySelector("main"),
			document.querySelector("#main"),
			document.documentElement
		];
		for (const candidate of candidates) {
			if (!candidate) continue;
			const bg = window.getComputedStyle(candidate).backgroundColor;
			if (!bg || bg === "transparent" || bg === "rgba(0, 0, 0, 0)") continue;
			return bg;
		}
		return null;
	}
	applySiteToneClass(root) {
		const nextClass = resolveSiteToneClass(location.hostname);
		if (this.currentSiteToneClass === nextClass) return;
		if (this.currentSiteToneClass) root.classList.remove(this.currentSiteToneClass);
		root.classList.add(nextClass);
		this.currentSiteToneClass = nextClass;
	}
	applyHostBackgroundVars(root, rgb, isLight) {
		const effective = rgb || (isLight ? {
			r: 246,
			g: 247,
			b: 249
		} : {
			r: 16,
			g: 18,
			b: 22
		});
		const luma = effective.r * .299 + effective.g * .587 + effective.b * .114;
		const rgbStr = `${effective.r}, ${effective.g}, ${effective.b}`;
		const lumaStr = luma.toFixed(2);
		if (root.style.getPropertyValue("--fc2-host-bg-rgb") !== rgbStr) root.style.setProperty("--fc2-host-bg-rgb", rgbStr);
		if (root.style.getPropertyValue("--fc2-host-bg-luma") !== lumaStr) root.style.setProperty("--fc2-host-bg-luma", lumaStr);
	}
	injectCss() {
		if (this.injectedStyle?.isConnected) return;
		const css = getConsolidatedCss({ minify: true });
		if (typeof GM_addStyle !== "undefined") this.injectedStyle = GM_addStyle(css);
		else {
			const style = document.createElement("style");
			style.textContent = css;
			document.head.appendChild(style);
			this.injectedStyle = style;
		}
		log$28.debug(`Injected CSS: ${Math.round(css.length / 1024)}KB`);
	}
	removePreferredSchemeListener() {
		if (!this.preferredSchemeQuery || !this.preferredSchemeHandler) return;
		if (typeof this.preferredSchemeQuery.removeEventListener === "function") this.preferredSchemeQuery.removeEventListener("change", this.preferredSchemeHandler);
		else if (typeof this.preferredSchemeQuery.removeListener === "function") this.preferredSchemeQuery.removeListener(this.preferredSchemeHandler);
	}
	addPreferredSchemeListener(detectTheme) {
		if (!this.preferredSchemeQuery) return;
		this.preferredSchemeHandler = () => detectTheme();
		if (typeof this.preferredSchemeQuery.addEventListener === "function") this.preferredSchemeQuery.addEventListener("change", this.preferredSchemeHandler);
		else if (typeof this.preferredSchemeQuery.addListener === "function") this.preferredSchemeQuery.addListener(this.preferredSchemeHandler);
	}
	setupThemeObservation(detectTheme) {
		const scheduleThemeDetection = () => {
			if (this.pendingThemeDetection) return;
			this.pendingThemeDetection = true;
			queueMicrotask(() => {
				this.pendingThemeDetection = false;
				detectTheme();
			});
		};
		const hasThemeSignal = (className) => {
			return /(^|\s)(dark|light|theme|night|day|nord|black|white)(-|\b)/i.test(className);
		};
		const startObserving = () => {
			if (!document.body) return;
			this.themeObserver?.disconnect();
			this.themeObserver = new MutationObserver((mutations) => {
				for (const mutation of mutations) {
					if (mutation.type !== "attributes") continue;
					const target = mutation.target;
					if (target === document.documentElement) {
						if (mutation.attributeName === "class" || mutation.attributeName === "data-theme" || mutation.attributeName === "data-mode") {
							scheduleThemeDetection();
							return;
						}
					}
					if (target === document.body && mutation.attributeName === "class" && hasThemeSignal(document.body.className)) {
						scheduleThemeDetection();
						return;
					}
				}
			});
			this.themeObserver.observe(document.documentElement, {
				attributes: true,
				attributeFilter: [
					"class",
					"data-theme",
					"data-mode"
				]
			});
			this.themeObserver.observe(document.body, {
				attributes: true,
				attributeFilter: ["class"]
			});
		};
		if (document.body) {
			detectTheme();
			startObserving();
			this.domReadyHandler = null;
			return;
		}
		this.domReadyHandler = () => {
			detectTheme();
			startObserving();
			this.domReadyHandler = null;
		};
		document.addEventListener("DOMContentLoaded", this.domReadyHandler, { once: true });
	}
	initThemeDetection() {
		let lastUpdate = 0;
		this.removePreferredSchemeListener();
		this.preferredSchemeHandler = null;
		this.preferredSchemeQuery = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-color-scheme: light)") : null;
		this.themeObserver?.disconnect();
		this.themeObserver = null;
		this.dataThemeObserver?.disconnect();
		this.dataThemeObserver = null;
		const syncDataTheme = () => {
			const root = document.documentElement;
			if (!root) return;
			const theme = root.getAttribute("data-theme");
			const hasClass = root.classList.contains("fc2-light-theme");
			if ((theme === "dark" || theme?.includes("dark")) && hasClass) {
				root.classList.remove("fc2-light-theme");
				clearStyleCache();
			} else if ((theme === "light" || theme === "cupcake" || theme === "winter") && !hasClass) {
				root.classList.add("fc2-light-theme");
				clearStyleCache();
			}
		};
		const detectTheme = () => {
			const root = document.documentElement;
			if (!root) return;
			const now = Date.now();
			if (now - lastUpdate < 500) return;
			lastUpdate = now;
			const applyTheme = (isLight) => {
				const hasClass = root.classList.contains("fc2-light-theme");
				if (isLight && !hasClass) {
					root.classList.add("fc2-light-theme");
					clearStyleCache();
				} else if (!isLight && hasClass) {
					root.classList.remove("fc2-light-theme");
					clearStyleCache();
				}
			};
			if (!document.body) {
				applyTheme(this.preferredSchemeQuery?.matches ?? false);
				this.applySiteToneClass(root);
				this.applyHostBackgroundVars(root, null, this.preferredSchemeQuery?.matches ?? false);
				return;
			}
			const htmlTheme = root.getAttribute("data-theme");
			if (htmlTheme === "dark" || htmlTheme?.includes("dark")) {
				applyTheme(false);
				this.applySiteToneClass(root);
				this.applyHostBackgroundVars(root, {
					r: 17,
					g: 24,
					b: 39
				}, false);
				return;
			}
			if (htmlTheme === "light" || htmlTheme === "cupcake" || htmlTheme === "winter") {
				applyTheme(true);
				this.applySiteToneClass(root);
				this.applyHostBackgroundVars(root, {
					r: 246,
					g: 247,
					b: 249
				}, true);
				return;
			}
			const hostBg = this.resolveHostBackgroundColor();
			const rgb = hostBg ? this.parseRgbColor(hostBg) : null;
			if (!rgb) {
				applyTheme(this.preferredSchemeQuery?.matches ?? false);
				this.applySiteToneClass(root);
				this.applyHostBackgroundVars(root, null, this.preferredSchemeQuery?.matches ?? false);
				return;
			}
			const isLight = rgb.r * .299 + rgb.g * .587 + rgb.b * .114 > 180;
			applyTheme(isLight);
			this.applySiteToneClass(root);
			this.applyHostBackgroundVars(root, rgb, isLight);
		};
		if (this.domReadyHandler) document.removeEventListener("DOMContentLoaded", this.domReadyHandler);
		this.setupThemeObservation(detectTheme);
		this.addPreferredSchemeListener(detectTheme);
		this.dataThemeObserver = new MutationObserver((mutations) => {
			for (const mutation of mutations) if (mutation.type === "attributes" && mutation.attributeName === "data-theme") {
				syncDataTheme();
				return;
			}
		});
		this.dataThemeObserver.observe(document.documentElement, {
			attributes: true,
			attributeFilter: ["data-theme"]
		});
		syncDataTheme();
	}
	getCss() {
		return getConsolidatedCss({ minify: false });
	}
	getCssSize() {
		const raw = getConsolidatedCss({ minify: false });
		const minified = getConsolidatedCss({ minify: true });
		return {
			raw: raw.length,
			minified: minified.length,
			ratio: Math.round((1 - minified.length / raw.length) * 100)
		};
	}
};
/** Constructed once from app/modules (composition root). */
function createStyleManager() {
	return new StyleService();
}
var ports$1 = null;
function configureProviderRegistry(next) {
	ports$1 = next;
}
function requirePorts$1() {
	if (!ports$1) throw new Error("[ProviderRegistry] Not configured. Call configureProviderRegistry from app/modules.");
	return ports$1;
}
var getActiveSyncProvider = () => {
	const { state, webdav, supabase } = requirePorts$1();
	const mode = state.get("syncMode");
	if (mode === "webdav") return webdav;
	if (mode === "supabase") return supabase;
	return null;
};
var getSupabaseAuthProvider = () => requirePorts$1().supabase;
var getWebDAVTestProvider = () => requirePorts$1().webdav;
/** Status priority: higher value supersedes lower on conflict */
var STATUS_PRIORITY = {
	watched: 1,
	downloaded: 2
};
/** Compares only the user-visible history fields, ignoring sync bookkeeping flags. */
var historyItemsEqual = (left, right) => left.id === right.id && left.timestamp === right.timestamp && left.status === right.status && left.updated_at === right.updated_at && Boolean(left.is_deleted) === Boolean(right.is_deleted);
/** True when user-visible history fields changed; ignores sync bookkeeping and version timestamps. */
var historySemanticChanged = (left, right) => left.id !== right.id || left.timestamp !== right.timestamp || left.status !== right.status || Boolean(left.is_deleted) !== Boolean(right.is_deleted);
/**
* Pure history merge for sync (local vs remote).
* Priority: preferRemote flag > tombstone > status priority > timestamp.
*/
var MergeEngine = class {
	static resolveRecordTime(updatedAt, fallbackTimestamp) {
		const parsed = updatedAt ? Date.parse(updatedAt) : NaN;
		return Number.isFinite(parsed) ? parsed : fallbackTimestamp;
	}
	static normalizeHistoryItem(item) {
		const fallbackTime = typeof item.timestamp === "number" ? item.timestamp : 0;
		return {
			...item,
			updated_at: new Date(this.resolveRecordTime(item.updated_at, fallbackTime)).toISOString()
		};
	}
	/** Public so sync providers can decide whether a local record should be pushed. */
	static chooseWinner(local, remote, preferRemote) {
		if (preferRemote) return remote;
		if (local.is_deleted && !remote.is_deleted) return local;
		if (!local.is_deleted && remote.is_deleted) return remote;
		const localPriority = STATUS_PRIORITY[local.status] ?? 0;
		const remotePriority = STATUS_PRIORITY[remote.status] ?? 0;
		if (localPriority !== remotePriority) return localPriority > remotePriority ? local : remote;
		const localTime = new Date(local.updated_at).getTime();
		const remoteTime = new Date(remote.updated_at).getTime();
		if (Number.isNaN(localTime) && Number.isNaN(remoteTime)) return local;
		if (Number.isNaN(localTime)) return remote;
		if (Number.isNaN(remoteTime)) return local;
		return localTime >= remoteTime ? local : remote;
	}
	static merge(localHistory, remoteHistory, preferRemote = false) {
		const historyMap = /* @__PURE__ */ new Map();
		if (remoteHistory) remoteHistory.forEach((r) => {
			const normalized = this.normalizeHistoryItem(r);
			historyMap.set(normalized.id, normalized);
		});
		for (const candidate of localHistory) {
			const local = this.normalizeHistoryItem(candidate);
			const remote = historyMap.get(local.id);
			if (!remote) historyMap.set(local.id, local);
			else historyMap.set(local.id, this.chooseWinner(local, remote, preferRemote));
		}
		return { history: Array.from(historyMap.values()) };
	}
};
var currentSiteHost = () => typeof location !== "undefined" && location.hostname ? location.hostname : "unknown";
var getSiteScopedStorageKey = (baseKey, host = currentSiteHost()) => `${baseKey}:${host}`;
var getSyncCursorStorageKey = exports("q",() => getSiteScopedStorageKey(STORAGE_KEYS.LAST_SYNC_TS));
var readSyncCursor = () => GM_getValue(getSyncCursorStorageKey(), UI_CONSTANTS.DEFAULT_TIMESTAMP) || UI_CONSTANTS.DEFAULT_TIMESTAMP;
var writeSyncCursor = (value) => {
	GM_setValue(getSyncCursorStorageKey(), value);
};
var resetSyncCursor = () => {
	GM_deleteValue(getSyncCursorStorageKey());
};
var getLastAutoSyncStorageKey = () => getSiteScopedStorageKey(STORAGE_KEYS.LAST_AUTO_SYNC_TS);
var readLastAutoSyncTs = () => GM_getValue(getLastAutoSyncStorageKey(), 0) || 0;
var writeLastAutoSyncTs = (value) => {
	GM_setValue(getLastAutoSyncStorageKey(), value);
};
var getWebDAVEtagStorageKey = () => getSiteScopedStorageKey(STORAGE_KEYS.WEBDAV_LAST_ETAG);
var readWebDAVEtag = () => GM_getValue(getWebDAVEtagStorageKey(), "") || "";
var writeWebDAVEtag = (value) => {
	GM_setValue(getWebDAVEtagStorageKey(), value);
};
var resetWebDAVEtag = () => {
	GM_deleteValue(getWebDAVEtagStorageKey());
};
var log$27 = Logger.scope("SupabaseClient");
var SupabaseClient = class {
	static getConfig() {
		let url = resolveService(STATE_SERVICE).proxy.supabaseUrl || "";
		if (url.endsWith("/")) url = url.slice(0, -1);
		url = url.trim();
		const key = (resolveService(STATE_SERVICE).proxy.supabaseKey || "").trim();
		if (key) log$27.debug(`Using API Key (len: ${key.length})`);
		else log$27.warn("No Supabase API Key configured");
		return {
			url,
			key
		};
	}
	static async request(endpoint, method, body = null, headers = {}) {
		const { url, key } = this.getConfig();
		if (!url || !key) throw new Error("No Supabase config");
		const isAuth = endpoint.includes("/auth/v1/");
		const isJwtKey = key.startsWith("eyJ");
		const requestHeaders = {
			apikey: key,
			"Content-Type": "application/json",
			...headers
		};
		if (!requestHeaders.Authorization && !isAuth && isJwtKey) requestHeaders.Authorization = `Bearer ${key}`;
		if (isAuth) log$27.debug(`Auth request: ${method} ${endpoint}`, {
			url: `${url}${endpoint}`,
			hasApiKey: !!key,
			apiKeyLength: key.length
		});
		log$27.trace(`Request: ${method} ${endpoint}`);
		try {
			const response = await http(`${url}${endpoint}`, {
				method,
				headers: requestHeaders,
				data: body ?? void 0
			});
			log$27.trace(`Response: ${method} ${endpoint}`, { response });
			return response;
		} catch (e) {
			const status = getHttpStatus(e);
			if (status === 401 || status === 403) {
				const { key } = this.getConfig();
				let hint = "";
				if (!key) hint = " (Key is missing)";
				else if (key.length < 20) hint = ` (Key is suspiciously short: ${key.length} chars)`;
				else if (key.startsWith("sb_")) hint = " (sb_ keys only work via apikey header, not Authorization: Bearer)";
				else if (!isAuth && !requestHeaders.Authorization) hint = " (Missing Authorization header)";
				const respBody = e.response;
				log$27.error(`Supabase Request failed: ${method} ${endpoint}${hint}${respBody ? ` | Response: ${respBody.slice(0, 500)}` : ""}`, e);
			} else log$27.error(`Request failed: ${method} ${endpoint}`, e);
			throw e;
		}
	}
	static async getAuthHeader() {
		const jwt = GM_getValue(STORAGE_KEYS.SUPABASE_JWT);
		if (jwt) try {
			const parts = jwt.split(".");
			if (parts.length >= 2 && parts[1]) {
				if (JSON.parse(atob(parts[1])).exp * 1e3 > Date.now() + 6e4) return `Bearer ${jwt}`;
			}
		} catch {}
		const refresh = GM_getValue(STORAGE_KEYS.SUPABASE_REFRESH);
		if (!refresh) return null;
		try {
			const data = await this.request(`${SUPABASE_ENDPOINTS.TOKEN}?grant_type=refresh_token`, "POST", { refresh_token: refresh });
			if (data?.access_token) {
				GM_setValue(STORAGE_KEYS.SUPABASE_JWT, data.access_token);
				GM_setValue(STORAGE_KEYS.SUPABASE_REFRESH, data.refresh_token);
				GM_setValue(STORAGE_KEYS.SYNC_USER_ID, data.user.id);
				return `Bearer ${data.access_token}`;
			}
		} catch (e) {
			log$27.error("Token refresh failed", e);
			const status = getHttpStatus(e);
			const response = e.response;
			if (status === 401 || response && response.includes("invalid_grant")) {
				[
					STORAGE_KEYS.SYNC_USER_ID,
					STORAGE_KEYS.SUPABASE_JWT,
					STORAGE_KEYS.SUPABASE_REFRESH
				].forEach((k) => GM_deleteValue(k));
				resetSyncCursor();
			}
		}
		return null;
	}
};
var notifyManualSyncStart = (isManual, syncingMessage) => {
	if (isManual) CoreEvents.emit(AppEvents.SHOW_TOAST, {
		message: syncingMessage,
		type: "info"
	});
};
var createSyncProviderErrorHandler = ({ mode, defaultErrorMessage, manualErrorToastPrefix, translate, buildSummary, logError }) => {
	return (error, isManual, traceId) => {
		const err = error;
		logError?.(error, traceId);
		resolveService(STATE_SERVICE).proxy.lastSyncResult = buildSummary({
			mode,
			status: SYNC_STATUS.ERROR,
			completedAt: (/* @__PURE__ */ new Date()).toISOString(),
			errorMessage: err.message || translate(defaultErrorMessage),
			translate
		});
		resolveService(STATE_SERVICE).proxy.syncStatus = SYNC_STATUS.ERROR;
		if (isManual) CoreEvents.emit(AppEvents.SHOW_TOAST, {
			message: translate(manualErrorToastPrefix) + (err.message || ""),
			type: "error"
		});
	};
};
var defaultTranslate = (key, params) => {
	let value = {
		syncSummaryFailed: "Sync failed",
		syncSummaryFailedWithMessage: "Sync failed: {message}",
		syncSummaryCompleted: "Sync completed",
		syncSummaryMode: "Mode: {mode}",
		syncSummaryPushed: "Pushed {count}",
		syncSummaryPulled: "Pulled {count}",
		syncSummaryDirty: "Dirty {count}"
	}[key] || key;
	if (params) Object.entries(params).forEach(([paramKey, paramValue]) => {
		value = value.replace(new RegExp(`{${paramKey}}`, "g"), String(paramValue));
	});
	return value;
};
var buildSyncSummaryText = (input) => {
	const translate = input.translate || defaultTranslate;
	if (input.summary && !input.excludeMode) return input.summary;
	if (input.status === "error") return input.errorMessage ? translate("syncSummaryFailedWithMessage", { message: input.errorMessage }) : translate("syncSummaryFailed");
	const parts = [
		input.excludeMode ? null : translate("syncSummaryMode", { mode: input.mode }),
		typeof input.pushedCount === "number" ? translate("syncSummaryPushed", { count: input.pushedCount }) : null,
		typeof input.pulledCount === "number" ? translate("syncSummaryPulled", { count: input.pulledCount }) : null,
		typeof input.dirtyCount === "number" ? translate("syncSummaryDirty", { count: input.dirtyCount }) : null
	].filter(Boolean);
	return parts.length > 0 ? parts.join(" · ") : translate("syncSummaryCompleted");
};
var createSyncResultSummary = (input) => ({
	mode: input.mode,
	status: input.status,
	startedAt: input.startedAt,
	completedAt: input.completedAt || (/* @__PURE__ */ new Date()).toISOString(),
	lastSyncTs: input.lastSyncTs,
	pushedCount: input.pushedCount,
	pulledCount: input.pulledCount,
	dirtyCount: input.dirtyCount,
	conflict: input.conflict || null,
	errorMessage: input.errorMessage,
	summary: buildSyncSummaryText(input),
	detailSummary: buildSyncSummaryText({
		...input,
		excludeMode: true
	})
});
var clearSupabaseSession = () => {
	[
		STORAGE_KEYS.SYNC_USER_ID,
		STORAGE_KEYS.SUPABASE_JWT,
		STORAGE_KEYS.SUPABASE_REFRESH
	].forEach((key) => {
		GM_deleteValue(key);
	});
	resetSyncCursor();
};
var persistSupabaseLogin = (data) => {
	GM_setValue(STORAGE_KEYS.SYNC_USER_ID, data.user.id);
	GM_setValue(STORAGE_KEYS.SUPABASE_JWT, data.access_token);
	GM_setValue(STORAGE_KEYS.SUPABASE_REFRESH, data.refresh_token);
	writeSyncCursor(UI_CONSTANTS.DEFAULT_TIMESTAMP);
	return data.user;
};
var buildSupabaseUpsertPayload = ({ dirty, userId, onProgress, log, traceId }) => {
	if (dirty.length === 0) return [];
	onProgress?.({
		phase: "Pushing local data",
		percent: 30
	});
	const payload = dirty.map((record) => {
		return {
			fc2_id: isNaN(Number(record.id)) ? record.id : parseInt(record.id, 10),
			last_watched_at: new Date(record.timestamp).toISOString(),
			status: record.status || "watched",
			is_deleted: !!record.is_deleted,
			user_id: userId || null
		};
	});
	if (payload.length > 1) {
		const firstKeys = Object.keys(payload[0]).sort().join(",");
		const lastKeys = Object.keys(payload[payload.length - 1]).sort().join(",");
		if (firstKeys !== lastKeys) log.error("Payload keys inconsistent", {
			first: firstKeys,
			last: lastKeys
		}, traceId);
	}
	return payload;
};
var normalizeSupabaseRemoteItem = (item) => ({
	id: String(item.fc2_id),
	timestamp: new Date(item.last_watched_at).getTime(),
	status: item.status || "watched",
	updated_at: item.updated_at,
	is_deleted: item.is_deleted ? 1 : 0,
	sync_dirty: 0
});
var buildSupabaseIdFilter = (ids) => {
	return `fc2_id=in.(${ids.map((id) => {
		const numeric = parseInt(id, 10);
		return Number.isNaN(numeric) ? JSON.stringify(id) : numeric;
	}).join(",")})`;
};
/** A dirty local record should be pushed only when it wins the same merge rules used on pull. */
var shouldPushLocalRecord = (local, remote) => {
	if (!remote) return true;
	const winner = MergeEngine.merge([local], [remote], false).history[0];
	return historyItemsEqual(winner, local);
};
var log$26 = Logger.scope("SupabaseProvider");
var SupabaseProvider = class {
	deps;
	constructor(deps) {
		this.deps = deps;
	}
	get data() {
		return this.deps.data;
	}
	get state() {
		return this.deps.state;
	}
	get history() {
		return this.deps.history;
	}
	name = "supabase";
	isSyncing = false;
	needsRetry = false;
	retryPreferRemote = false;
	retryForceRefresh = false;
	_onProgress = null;
	onInit() {}
	logout(silent = false) {
		clearSupabaseSession();
		if (!silent) {
			CoreEvents.emit(AppEvents.SHOW_TOAST, {
				message: t("alertLoggedOut"),
				type: "success"
			});
			setTimeout(() => location.reload(), TIMING.RELOAD_DELAY_FAST);
		}
	}
	async login(email, password) {
		const data = await SupabaseClient.request(`${SUPABASE_ENDPOINTS.TOKEN}?grant_type=password`, "POST", {
			email,
			password
		});
		if (data?.access_token) return persistSupabaseLogin(data);
		throw new Error("Login failed");
	}
	async signup(email, password) {
		return SupabaseClient.request(SUPABASE_ENDPOINTS.SIGNUP, "POST", {
			email,
			password
		});
	}
	get onProgress() {
		return this._onProgress;
	}
	set onProgress(value) {
		this._onProgress = value;
	}
	handleMissingAuth(auth, isManual, traceId) {
		if (auth) return false;
		log$26.debug("No auth session, skipping sync", void 0, traceId);
		this.state.proxy.syncStatus = SYNC_STATUS.IDLE;
		if (isManual) CoreEvents.emit(AppEvents.SHOW_TOAST, {
			message: t("alertLoginRequired"),
			type: "warn"
		});
		return true;
	}
	resolveLastSync(forceRefresh) {
		return forceRefresh ? UI_CONSTANTS.DEFAULT_TIMESTAMP : readSyncCursor();
	}
	async finalizeSuccessfulSync(syncStartedAt, isManual, pushedCount, nextCursor, pulledChanged) {
		if (pulledChanged > 0) await this.history.load();
		writeSyncCursor(nextCursor);
		this.state.proxy.lastSyncResult = createSyncResultSummary({
			mode: "supabase",
			status: SYNC_STATUS.SUCCESS,
			startedAt: syncStartedAt,
			completedAt: (/* @__PURE__ */ new Date()).toISOString(),
			lastSyncTs: nextCursor,
			pushedCount,
			dirtyCount: await this.data.db.history.where("sync_dirty").equals(1).count(),
			translate: t
		});
		this.state.proxy.syncStatus = SYNC_STATUS.SUCCESS;
		log$26.info("Sync completed");
		if (isManual) CoreEvents.emit(AppEvents.SHOW_TOAST, {
			message: t("alertSupabaseSyncSuccess"),
			type: "success"
		});
	}
	handleSyncError(error, isManual, traceId) {
		createSyncProviderErrorHandler({
			mode: "supabase",
			defaultErrorMessage: "syncErrorSupabase",
			manualErrorToastPrefix: "alertSupabaseSyncError",
			translate: t,
			buildSummary: (input) => createSyncResultSummary(input),
			logError: (err, trace) => log$26.error("Sync failed", err, trace)
		})(error, isManual, traceId);
	}
	async fetchRemoteRows(auth, ids) {
		if (ids.length === 0) return /* @__PURE__ */ new Map();
		const filter = buildSupabaseIdFilter(ids);
		const remote = await RetryManager.executeWithRetry(() => SupabaseClient.request(`${SUPABASE_ENDPOINTS.USER_HISTORY}?${filter}&select=fc2_id,last_watched_at,is_deleted,updated_at,status`, "GET", null, { Authorization: auth }), RetryManager.presets.supabaseSync);
		return new Map((remote || []).map((item) => [String(item.fc2_id), normalizeSupabaseRemoteItem(item)]));
	}
	async finalizePushedRecords(auth, records) {
		try {
			const remoteById = await this.fetchRemoteRows(auth, records.map((record) => record.id));
			if (remoteById.size === 0) return;
			const pushedById = new Map(records.map((record) => [record.id, record]));
			await this.data.db.history.where("id").anyOf(Array.from(remoteById.keys())).modify((row) => {
				const pushed = pushedById.get(row.id);
				const remote = remoteById.get(row.id);
				if (!pushed || !remote?.updated_at || !historyItemsEqual(row, pushed)) return;
				row.updated_at = remote.updated_at;
				row.sync_dirty = 0;
				row.retry_count = 0;
			});
		} catch (error) {
			log$26.warn("Failed to finalize pushed records", error);
		}
	}
	async reconcileRemoteWinners(records, remoteById) {
		const remoteWinners = records.filter((record) => {
			const remote = remoteById.get(record.id);
			return !!remote && !shouldPushLocalRecord(record, remote);
		});
		if (remoteWinners.length === 0) return;
		const pushedById = new Map(remoteWinners.map((record) => [record.id, record]));
		await this.data.db.transaction("rw", this.data.db.history, async () => {
			await this.data.db.history.where("id").anyOf(remoteWinners.map((record) => record.id)).modify((row) => {
				const pushed = pushedById.get(row.id);
				const remote = remoteById.get(row.id);
				if (!pushed || !remote || !historyItemsEqual(row, pushed)) return;
				row.timestamp = remote.timestamp;
				row.status = remote.status;
				row.updated_at = remote.updated_at;
				row.is_deleted = remote.is_deleted ?? 0;
				row.sync_dirty = 0;
				row.retry_count = 0;
			});
		});
		await this.history.load();
	}
	async incrementRetryCount(records) {
		await this.data.db.history.where("id").anyOf(records.map((record) => record.id)).modify((record) => {
			record.retry_count = (record.retry_count || 0) + 1;
		});
	}
	async uploadRecords(auth, records, traceId) {
		const payload = buildSupabaseUpsertPayload({
			dirty: records,
			userId: GM_getValue(STORAGE_KEYS.SYNC_USER_ID),
			onProgress: this._onProgress,
			log: log$26,
			traceId
		});
		try {
			await RetryManager.executeWithRetry(() => SupabaseClient.request(SUPABASE_ENDPOINTS.USER_HISTORY, "POST", payload, {
				Authorization: auth,
				Prefer: "resolution=merge-duplicates"
			}), RetryManager.presets.supabaseSync);
			await this.finalizePushedRecords(auth, records);
			return records.length;
		} catch (error) {
			await this.incrementRetryCount(records);
			throw error;
		}
	}
	async pushDirtyItems(auth, traceId) {
		const CHUNK_SIZE = 50;
		let pushed = 0;
		while (true) {
			const dirty = await this.data.db.history.where("sync_dirty").equals(1).limit(CHUNK_SIZE).toArray();
			if (dirty.length === 0) break;
			try {
				const remoteById = await this.fetchRemoteRows(auth, dirty.map((record) => record.id));
				const toPush = dirty.filter((record) => shouldPushLocalRecord(record, remoteById.get(record.id)));
				await this.reconcileRemoteWinners(dirty, remoteById);
				if (toPush.length === 0) break;
				pushed += await this.uploadRecords(auth, toPush, traceId);
			} catch (error) {
				await this.incrementRetryCount(dirty);
				throw error;
			}
			if (dirty.length < CHUNK_SIZE) break;
		}
		if (pushed > 0) log$26.info(`Pushed ${pushed} dirty items in chunks`);
		return pushed;
	}
	async pullRemoteHistory(auth, lastSync, preferRemote) {
		this._onProgress?.({
			phase: "Fetching remote data",
			percent: 60
		});
		let page = 0;
		let hasMore = true;
		let changedCount = 0;
		let maxUpdatedAt = lastSync;
		const pendingPush = [];
		while (hasMore) {
			const remote = await RetryManager.executeWithRetry(() => SupabaseClient.request(`${SUPABASE_ENDPOINTS.USER_HISTORY}?updated_at=gt.${encodeURIComponent(lastSync)}&select=fc2_id,last_watched_at,is_deleted,updated_at,status&order=updated_at.asc`, "GET", null, {
				Authorization: auth,
				Range: `${page * 1e3}-${(page + 1) * 1e3 - 1}`
			}), RetryManager.presets.supabaseSync);
			if (!remote?.length) {
				hasMore = false;
				continue;
			}
			const remoteItems = remote.map(normalizeSupabaseRemoteItem);
			remoteItems.forEach((item) => {
				if (item.updated_at > maxUpdatedAt) maxUpdatedAt = item.updated_at;
			});
			const ids = remoteItems.map((item) => item.id);
			const localRows = ids.length ? await this.data.db.history.where("id").anyOf(ids).toArray() : [];
			const localById = new Map(localRows.map((row) => [row.id, row]));
			const merged = remoteItems.map((remoteItem) => {
				const local = localById.get(remoteItem.id);
				if (!local) return {
					...remoteItem,
					sync_dirty: 0
				};
				const winner = MergeEngine.merge([local], [remoteItem], preferRemote).history[0];
				if (historyItemsEqual(winner, local) && local.sync_dirty) {
					const row = {
						...winner,
						sync_dirty: 1
					};
					pendingPush.push(row);
					return row;
				}
				return {
					...winner,
					sync_dirty: 0
				};
			});
			await this.data.db.transaction("rw", this.data.db.history, async () => {
				await this.data.db.history.bulkPut(merged);
			});
			merged.forEach((item) => {
				const local = localById.get(item.id);
				if (!local || historySemanticChanged(local, item)) changedCount += 1;
			});
			hasMore = remote.length >= 1e3;
			if (hasMore) page += 1;
		}
		return {
			changedCount,
			maxUpdatedAt: maxUpdatedAt > lastSync ? maxUpdatedAt : lastSync,
			pendingPush
		};
	}
	async performSync(isManual = false, forceRefresh = false, preferRemote = false, traceId) {
		if (this.isSyncing) {
			this.needsRetry = true;
			this.retryForceRefresh = this.retryForceRefresh || forceRefresh;
			this.retryPreferRemote = this.retryPreferRemote || preferRemote;
			return;
		}
		if (preferRemote) forceRefresh = true;
		Logger.group("Supabase", `Sync (Force: ${forceRefresh})`, traceId);
		this.isSyncing = true;
		notifyManualSyncStart(isManual, t("labelSyncing"));
		try {
			this.state.proxy.syncStatus = SYNC_STATUS.SYNCING;
			const auth = await SupabaseClient.getAuthHeader();
			if (this.handleMissingAuth(auth, isManual, traceId)) return;
			const lastSync = this.resolveLastSync(forceRefresh);
			const syncStartedAt = (/* @__PURE__ */ new Date()).toISOString();
			let pushedCount = 0;
			if (!preferRemote) pushedCount += await this.pushDirtyItems(auth, traceId);
			const { changedCount, maxUpdatedAt, pendingPush } = await this.pullRemoteHistory(auth, lastSync, preferRemote);
			if (!preferRemote && pendingPush.length > 0) pushedCount += await this.uploadRecords(auth, pendingPush, traceId);
			await this.finalizeSuccessfulSync(syncStartedAt, isManual, pushedCount, maxUpdatedAt, changedCount);
		} catch (error) {
			this.handleSyncError(error, isManual, traceId);
		} finally {
			this.isSyncing = false;
			Logger.groupEnd();
			if (this.needsRetry) {
				this.needsRetry = false;
				const forceRefreshRetry = this.retryForceRefresh;
				const preferRemoteRetry = this.retryPreferRemote;
				this.retryForceRefresh = false;
				this.retryPreferRemote = false;
				await this.performSync(false, forceRefreshRetry, preferRemoteRetry, traceId).catch((e) => log$26.error("Retry sync failed", e));
			}
		}
	}
};
/** Constructed once from app/modules (composition root). */
function createSupabaseProvider(deps) {
	return new SupabaseProvider(deps);
}
var log$25 = Logger.scope("WebDAV");
var WebDAVClient = class {
	static knownDirectories = /* @__PURE__ */ new Set();
	static getAuthHeader() {
		const { webdavUser, webdavPass } = resolveService(STATE_SERVICE).proxy;
		return "Basic " + btoa(`${webdavUser}:${webdavPass}`);
	}
	static async request(method, url, body = null, headers = {}, responseType = "text") {
		return new Promise((resolve, reject) => {
			GM_xmlhttpRequest({
				method,
				url,
				headers: {
					Authorization: this.getAuthHeader(),
					...headers
				},
				data: body,
				responseType,
				timeout: 3e4,
				onload: resolve,
				onerror: reject,
				ontimeout: () => reject(/* @__PURE__ */ new Error("WebDAV Timeout"))
			});
		});
	}
	static async ensureDirectory(baseUrl, fullPath) {
		const parts = fullPath.split("/").filter((p) => p && !p.includes("."));
		let currentPath = baseUrl.replace(/\/$/, "");
		for (const part of parts) {
			currentPath += "/" + part;
			if (this.knownDirectories.has(currentPath)) continue;
			try {
				const res = await this.request("PROPFIND", currentPath, null, { Depth: "0" });
				if (res.status === 404) {
					log$25.debug(`Creating directory: ${currentPath}`);
					await this.request("MKCOL", currentPath);
				} else if (res.status >= 400) throw new Error(`Failed to access WebDAV directory ${currentPath}: ${res.status}`);
				this.knownDirectories.add(currentPath);
			} catch (e) {
				log$25.error(`EnsureDirectory failed: ${currentPath}`, e);
				throw e;
			}
		}
	}
	static async fetchFile(url) {
		try {
			const res = await this.request("GET", url, null, {}, "blob");
			if (res.status === 200) {
				const etag = res.responseHeaders.match(/etag:\s*(.*)/i)?.[1]?.replace(/"/g, "").trim();
				const raw = res.response;
				const text = raw instanceof Blob ? await GzipService.decompress(raw) : String(res.responseText || raw || "");
				return {
					ok: true,
					data: JSON.parse(text),
					etag
				};
			}
			log$25.error(`Fetch returned status ${res.status} for ${url}`);
			return {
				ok: false,
				error: /* @__PURE__ */ new Error(`HTTP ${res.status}`)
			};
		} catch (e) {
			log$25.error(`Fetch failed for ${url}`, e);
			return {
				ok: false,
				error: e
			};
		}
	}
};
var log$24 = Logger.scope("ConflictLog");
var MAX_CONFLICT_ENTRIES = 50;
/**
* Append a conflict summary to the persistent conflict log.
* Old entries are pruned when the log exceeds MAX_CONFLICT_ENTRIES.
*/
var appendConflictLog = (summary, syncMode) => {
	try {
		const existing = getConflictLog();
		const entry = {
			timestamp: (/* @__PURE__ */ new Date()).toISOString(),
			summary,
			syncMode
		};
		existing.unshift(entry);
		if (existing.length > MAX_CONFLICT_ENTRIES) existing.length = MAX_CONFLICT_ENTRIES;
		Storage.set(STORAGE_KEYS.CONFLICT_LOG, existing);
		log$24.info(`Conflict logged: ${summary.localNewer} local, ${summary.remoteNewer} remote, ${summary.merged} merged`);
	} catch (e) {
		log$24.warn("Failed to persist conflict log", e);
	}
};
/**
* Retrieve the conflict log (most recent first).
*/
var getConflictLog = () => {
	try {
		return Storage.get(STORAGE_KEYS.CONFLICT_LOG, []) || [];
	} catch {
		return [];
	}
};
/**
* Calculate SHA-256 hash of JSON-serialized data
* @param data - Data to hash (will be JSON-serialized)
* @returns Hex-encoded SHA-256 hash string
*/
async function calculateSHA256(data) {
	return calculateSHA256Text(JSON.stringify(data));
}
/**
* Calculate SHA-256 hash of an already-serialized string.
* Lets callers hash the exact bytes they are about to upload.
*/
async function calculateSHA256Text(text) {
	const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
	return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
}
var extractWebDAVEtag = (responseHeaders) => {
	return responseHeaders.match(/etag:\s*(.*)/i)?.[1]?.replace(/"/g, "").trim();
};
var shouldSkipWebDAVSync = ({ preferRemote, forceRefresh, remoteETag, lastEtag, dirtyCount }) => {
	return !preferRemote && !forceRefresh && !!remoteETag && lastEtag === remoteETag && dirtyCount === 0;
};
var createWebDAVTarget = (webdavUrl, webdavPath) => {
	return `${webdavUrl.replace(/\/$/, "")}/${webdavPath}`;
};
var createWebDAVSyncPayload = async ({ mergedHistory }) => {
	const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
	const historyJson = JSON.stringify(mergedHistory);
	const checksum = await calculateSHA256Text(historyJson);
	return {
		version: 2,
		updated_at: updatedAt,
		history: mergedHistory,
		checksum,
		serialized: `{"version":2,"updated_at":${JSON.stringify(updatedAt)},"history":${historyJson},"checksum":${JSON.stringify(checksum)}}`
	};
};
var clearWebDAVSession = () => {
	[
		STORAGE_KEYS.WEBDAV_URL,
		STORAGE_KEYS.WEBDAV_USER,
		STORAGE_KEYS.WEBDAV_PASS,
		STORAGE_KEYS.WEBDAV_PATH
	].forEach((key) => {
		GM_deleteValue(key);
	});
	resetWebDAVEtag();
};
var acquireWebDAVSyncLock = ({ isManual, emitLocked }) => {
	const now = Date.now();
	if (now - GM_getValue(STORAGE_KEYS.WEBDAV_SYNC_LOCK, 0) < 1e4) {
		if (isManual) emitLocked();
		return false;
	}
	GM_setValue(STORAGE_KEYS.WEBDAV_SYNC_LOCK, now);
	return true;
};
var shouldFetchWebDAVRemoteState = ({ forceRefresh, preferRemote, lastEtag, remoteETag, headStatus }) => {
	return forceRefresh || preferRemote || !lastEtag || lastEtag !== remoteETag || headStatus === 404;
};
var verifyRemoteChecksum = async ({ remoteData }) => {
	if (remoteData.checksum && await calculateSHA256(remoteData.history) !== remoteData.checksum) throw new Error("Remote checksum mismatch");
};
var log$23 = Logger.scope("WebDAVProvider");
var WebDAVProvider = class {
	deps;
	constructor(deps) {
		this.deps = deps;
	}
	get data() {
		return this.deps.data;
	}
	get state() {
		return this.deps.state;
	}
	get history() {
		return this.deps.history;
	}
	name = "webdav";
	isSyncing = false;
	syncLockHeartbeat = null;
	onInit() {}
	checkSyncLock(isManual) {
		return acquireWebDAVSyncLock({
			isManual,
			emitLocked: () => {
				CoreEvents.emit(AppEvents.SHOW_TOAST, {
					message: t("alertSyncLockActive"),
					type: "warn"
				});
			}
		});
	}
	startSyncLockHeartbeat() {
		this.stopSyncLockHeartbeat();
		this.syncLockHeartbeat = setInterval(() => {
			GM_setValue(STORAGE_KEYS.WEBDAV_SYNC_LOCK, Date.now());
		}, 8e3);
	}
	stopSyncLockHeartbeat() {
		if (!this.syncLockHeartbeat) return;
		clearInterval(this.syncLockHeartbeat);
		this.syncLockHeartbeat = null;
	}
	resolveSyncTarget() {
		const { webdavUrl, webdavPath } = this.state.proxy;
		if (!webdavUrl || !webdavPath) return null;
		return {
			webdavUrl,
			webdavPath
		};
	}
	markSyncUpToDate(isManual) {
		this.state.proxy.lastSyncResult = createSyncResultSummary({
			mode: "webdav",
			status: SYNC_STATUS.SUCCESS,
			completedAt: (/* @__PURE__ */ new Date()).toISOString(),
			summary: t("syncSummaryUpToDate"),
			translate: t
		});
		this.state.proxy.syncStatus = SYNC_STATUS.SUCCESS;
		if (isManual) CoreEvents.emit(AppEvents.SHOW_TOAST, {
			message: t("alertAlreadyUpToDate"),
			type: "info"
		});
	}
	handleSyncError(error, isManual, traceId) {
		createSyncProviderErrorHandler({
			mode: "webdav",
			defaultErrorMessage: "syncErrorWebdav",
			manualErrorToastPrefix: "alertWebDAVSyncError",
			translate: t,
			buildSummary: (input) => createSyncResultSummary(input),
			logError: (err, trace) => log$23.error("Sync error", err, trace)
		})(error, isManual, traceId);
	}
	async fetchRemoteState(targetUrl, forceRefresh, preferRemote) {
		const headResponse = await WebDAVClient.request("HEAD", targetUrl);
		const remoteETag = headResponse.status === 200 ? extractWebDAVEtag(headResponse.responseHeaders) : void 0;
		const lastEtag = readWebDAVEtag();
		if (shouldSkipWebDAVSync({
			preferRemote,
			forceRefresh,
			remoteETag,
			lastEtag,
			dirtyCount: await this.data.db.history.where("sync_dirty").equals(1).count()
		})) return {
			remoteETag,
			shouldSkip: true
		};
		if (headResponse.status === 404) {
			const legacyUrl = `${targetUrl}.gz`;
			if ((await WebDAVClient.request("HEAD", legacyUrl)).status === 200) {
				const legacy = await WebDAVClient.fetchFile(legacyUrl);
				if (!legacy.ok) throw legacy.error instanceof Error ? legacy.error : /* @__PURE__ */ new Error("Failed to migrate legacy WebDAV file");
				await verifyRemoteChecksum({ remoteData: legacy.data });
				return {
					remoteETag: void 0,
					remoteData: legacy.data,
					shouldSkip: false
				};
			}
		}
		let remoteData;
		if (shouldFetchWebDAVRemoteState({
			forceRefresh,
			preferRemote,
			lastEtag,
			remoteETag,
			headStatus: headResponse.status
		})) {
			const fetched = await WebDAVClient.fetchFile(targetUrl);
			if (!fetched.ok) {
				if (headResponse.status !== 404) throw fetched.error instanceof Error ? fetched.error : /* @__PURE__ */ new Error(`Failed to fetch remote WebDAV state (HTTP ${headResponse.status})`);
			} else {
				await verifyRemoteChecksum({ remoteData: fetched.data });
				remoteData = fetched.data;
			}
		}
		return {
			remoteETag,
			remoteData,
			shouldSkip: false
		};
	}
	async mergeAndUpload(targetUrl, useGzip, remoteETag, remoteData, preferRemote, isManual, retryOnConflict, traceId, propagateErrors = false) {
		const localHistory = await this.data.db.history.toArray();
		const { history: mergedHistory } = MergeEngine.merge(localHistory, remoteData?.history, preferRemote);
		const { serialized } = await createWebDAVSyncPayload({ mergedHistory });
		const putHeaders = { "Content-Type": useGzip ? "application/gzip" : "application/json" };
		if (remoteETag) putHeaders["If-Match"] = `"${remoteETag.replace(/"/g, "")}"`;
		const body = useGzip ? await GzipService.compress(serialized) : serialized;
		const response = await WebDAVClient.request("PUT", targetUrl, body, putHeaders);
		if (response.status >= 200 && response.status < 300) {
			await this.handleSuccessfulUpload(response.responseHeaders, remoteETag, mergedHistory, isManual);
			return;
		}
		if (response.status === 412 && retryOnConflict) {
			const conflictSummary = {
				localNewer: localHistory.length,
				remoteNewer: remoteData?.history?.length ?? 0,
				merged: mergedHistory.length,
				explanation: t("syncConflictWebdavRetry")
			};
			appendConflictLog(conflictSummary, "webdav");
			this.state.proxy.lastSyncResult = createSyncResultSummary({
				mode: "webdav",
				status: SYNC_STATUS.CONFLICT,
				completedAt: (/* @__PURE__ */ new Date()).toISOString(),
				summary: t("syncSummaryConflictRetry"),
				conflict: conflictSummary,
				translate: t
			});
			this.state.proxy.syncStatus = SYNC_STATUS.CONFLICT;
			return this.runSync(isManual, true, false, false, traceId, propagateErrors, true);
		}
		throw new Error(`WebDAV PUT failed: ${response.status}`);
	}
	async handleSuccessfulUpload(responseHeaders, remoteETag, mergedHistory, isManual) {
		const newEtag = extractWebDAVEtag(responseHeaders) || remoteETag;
		if (newEtag) writeWebDAVEtag(newEtag);
		const uploadedRemote = mergedHistory.map((history) => ({
			...history,
			sync_dirty: 0,
			retry_count: 0
		}));
		let reloadNeeded = false;
		await this.data.db.transaction("rw", this.data.db.history, async () => {
			const currentRows = await this.data.db.history.toArray();
			const currentById = new Map(currentRows.map((row) => [row.id, row]));
			const { history: reconciled } = MergeEngine.merge(currentRows, uploadedRemote, false);
			const writes = reconciled.map((row) => {
				const current = currentById.get(row.id);
				const localWon = !!current && !!current.sync_dirty && historyItemsEqual(row, current);
				return {
					row: {
						...row,
						sync_dirty: localWon ? 1 : 0,
						retry_count: localWon ? current?.retry_count || 0 : 0
					},
					current
				};
			}).filter(({ row, current }) => {
				const bookkeepingChanged = !current || current.sync_dirty !== row.sync_dirty || (current.retry_count || 0) !== (row.retry_count || 0);
				return !current || !historyItemsEqual(current, row) || bookkeepingChanged;
			}).map(({ row }) => row);
			if (writes.length > 0) await this.data.db.history.bulkPut(writes);
			reloadNeeded = reconciled.some((row) => {
				const current = currentById.get(row.id);
				return !current || historySemanticChanged(current, row);
			});
		});
		if (reloadNeeded) await this.history.load();
		const completedAt = (/* @__PURE__ */ new Date()).toISOString();
		writeSyncCursor(completedAt);
		this.state.proxy.lastSyncResult = createSyncResultSummary({
			mode: "webdav",
			status: SYNC_STATUS.SUCCESS,
			completedAt,
			lastSyncTs: completedAt,
			dirtyCount: await this.data.db.history.where("sync_dirty").equals(1).count(),
			translate: t
		});
		this.state.proxy.syncStatus = SYNC_STATUS.SUCCESS;
		if (isManual) CoreEvents.emit(AppEvents.SHOW_TOAST, {
			message: t("alertWebDAVSyncSuccess"),
			type: "success"
		});
	}
	async runSync(isManual = false, forceRefresh = false, retryOnConflict = true, preferRemote = false, traceId, propagateErrors = false, isRetry = false) {
		if (this.isSyncing && !isRetry || !isRetry && !this.checkSyncLock(isManual)) return;
		const syncTarget = this.resolveSyncTarget();
		if (!syncTarget) return;
		Logger.group("WebDAV", `Sync (Force: ${forceRefresh}, CloudFirst: ${preferRemote})`, traceId);
		this.isSyncing = true;
		this.state.proxy.syncStatus = SYNC_STATUS.SYNCING;
		this.startSyncLockHeartbeat();
		try {
			await WebDAVClient.ensureDirectory(syncTarget.webdavUrl, syncTarget.webdavPath);
			const useGzip = GzipService.isSupported();
			const targetUrl = createWebDAVTarget(syncTarget.webdavUrl, syncTarget.webdavPath);
			const { remoteETag, remoteData, shouldSkip } = await this.fetchRemoteState(targetUrl, forceRefresh, preferRemote);
			if (shouldSkip) {
				this.markSyncUpToDate(isManual);
				return;
			}
			await this.mergeAndUpload(targetUrl, useGzip, remoteETag, remoteData, preferRemote, isManual, retryOnConflict, traceId, propagateErrors);
		} catch (error) {
			if (propagateErrors) throw error;
			this.handleSyncError(error, isManual, traceId);
		} finally {
			this.isSyncing = false;
			this.stopSyncLockHeartbeat();
			GM_setValue(STORAGE_KEYS.WEBDAV_SYNC_LOCK, 0);
			Logger.groupEnd();
		}
	}
	async test() {
		const { webdavUrl } = this.state.proxy;
		if (!webdavUrl) throw new Error("URL is empty");
		return WebDAVClient.request("PROPFIND", webdavUrl.replace(/\/$/, "") + "/", null, { Depth: "0" });
	}
	logout() {
		clearWebDAVSession();
	}
	async performSync(isManual = false, forceRefresh = false, preferRemote = false, traceId) {
		if (forceRefresh || preferRemote) resetWebDAVEtag();
		notifyManualSyncStart(isManual, t("labelSyncing"));
		try {
			await RetryManager.executeWithRetry(() => this.runSync(isManual, forceRefresh, true, preferRemote, traceId, true), RetryManager.presets.sync);
		} catch (error) {
			this.handleSyncError(error, isManual, traceId);
		}
	}
};
/** Constructed once from app/modules (composition root). */
function createWebDAVProvider(deps) {
	return new WebDAVProvider(deps);
}
var MAX_HISTORY = 200;
var roundMetric = (value) => Math.round(value * 100) / 100;
var PerformanceMonitorImpl = class {
	history = [];
	record(name, durationMs, metadata) {
		const completedAt = /* @__PURE__ */ new Date();
		const startedAt = new Date(completedAt.getTime() - Math.max(0, durationMs));
		const entry = {
			name,
			durationMs: roundMetric(durationMs),
			startedAt: startedAt.toISOString(),
			completedAt: completedAt.toISOString(),
			metadata: metadata ? { ...metadata } : void 0
		};
		this.history.push(entry);
		if (this.history.length > MAX_HISTORY) this.history.splice(0, this.history.length - MAX_HISTORY);
		return entry;
	}
	measureSync(name, callback, metadata) {
		const start = performance.now();
		try {
			return callback();
		} finally {
			this.record(name, performance.now() - start, metadata);
		}
	}
	async measure(name, callback, metadata) {
		const start = performance.now();
		try {
			return await callback();
		} finally {
			this.record(name, performance.now() - start, metadata);
		}
	}
	getSummary() {
		const metrics = this.history.reduce((acc, entry) => {
			const current = acc[entry.name] || {
				count: 0,
				totalMs: 0,
				avgMs: 0,
				maxMs: 0,
				lastMs: 0
			};
			current.count += 1;
			current.totalMs = roundMetric(current.totalMs + entry.durationMs);
			current.maxMs = roundMetric(Math.max(current.maxMs, entry.durationMs));
			current.lastMs = roundMetric(entry.durationMs);
			current.avgMs = roundMetric(current.totalMs / current.count);
			acc[entry.name] = current;
			return acc;
		}, {});
		return {
			updatedAt: this.history.length > 0 ? this.history[this.history.length - 1]?.completedAt || null : null,
			metrics
		};
	}
	exportSnapshot() {
		return {
			summary: this.getSummary(),
			history: this.history.map((entry) => ({
				...entry,
				metadata: entry.metadata ? { ...entry.metadata } : void 0
			}))
		};
	}
	clear() {
		this.history = [];
	}
};
var PerformanceMonitor = new PerformanceMonitorImpl();
var log$22 = Logger.scope("SyncLock");
var SYNC_LOCK_NAME = "fc2_sync_lock";
var FALLBACK_LOCK_KEY = "fc2_sync_lock_fallback";
var FALLBACK_LOCK_TTL_MS = 6e4;
var SyncLockCoordinator = class {
	async runWithLock(task, isManual = false) {
		if (typeof navigator !== "undefined" && navigator.locks) return await navigator.locks.request(SYNC_LOCK_NAME, { ifAvailable: true }, async (lock) => {
			if (!lock) {
				this.handleLockedSync(isManual);
				return false;
			}
			await task();
			return true;
		});
		log$22.debug("navigator.locks not available, using fallback lock");
		const now = Date.now();
		if (now < GM_getValue(FALLBACK_LOCK_KEY, 0)) {
			this.handleLockedSync(isManual);
			return false;
		}
		GM_setValue(FALLBACK_LOCK_KEY, now + FALLBACK_LOCK_TTL_MS);
		try {
			await task();
		} finally {
			GM_deleteValue(FALLBACK_LOCK_KEY);
		}
		return true;
	}
	handleLockedSync(isManual) {
		if (isManual) {
			log$22.info("Manual sync skipped: lock held by another tab");
			CoreEvents.emit(AppEvents.SHOW_TOAST, {
				message: t("alertSyncLocked") || "Sync already in progress in another tab",
				type: "warn"
			});
			return;
		}
		log$22.debug("Auto-sync skipped: lock held by another tab");
	}
};
var syncLockCoordinator = new SyncLockCoordinator();
var log$21 = Logger.scope("SyncRecovery");
var SyncRecoveryActions = class {
	dependencies;
	constructor(dependencies) {
		this.dependencies = dependencies;
	}
	async forceFullSync(skipConfirm = false) {
		if (!skipConfirm) {
			if (!this.dependencies.confirmAction(this.dependencies.getPushAllMessage())) return false;
		}
		try {
			await this.dependencies.markAllHistoryDirty();
			this.dependencies.resetLastSync();
			await this.dependencies.performSync(true, true, false);
			return true;
		} catch (e) {
			log$21.error("forceFullSync failed", e);
			return false;
		}
	}
	async forcePullSync(skipConfirm = false) {
		if (!skipConfirm) {
			if (!this.dependencies.confirmAction(this.dependencies.getPullAllMessage())) return false;
		}
		try {
			this.dependencies.resetLastSync();
			await this.dependencies.performSync(true, true, true);
			return true;
		} catch (e) {
			log$21.error("forcePullSync failed", e);
			return false;
		}
	}
};
var log$20 = Logger.scope("SyncScheduler");
var SyncScheduler = class {
	dependencies;
	initialSyncTimer = null;
	syncRequestTimer = null;
	periodicSyncTimer = null;
	detachHistoryChanged = null;
	detachCollectionUpdated = null;
	detachStateChanged = null;
	onlineHandler = null;
	constructor(dependencies) {
		this.dependencies = dependencies;
	}
	recordAutoSyncTimestamp() {
		writeLastAutoSyncTs(Date.now());
	}
	start() {
		log$20.debug("Starting auto-sync scheduler");
		this.clearInitialSyncTimer();
		this.detachHistoryChanged?.();
		this.detachCollectionUpdated?.();
		this.detachStateChanged?.();
		const mode = this.dependencies.state.get("syncMode");
		const interval = this.dependencies.state.get("syncInterval");
		if (mode !== "none" && interval !== -1) {
			this.initialSyncTimer = setTimeout(() => this.dependencies.performSync(false).then(() => this.recordAutoSyncTimestamp()).catch((error) => {
				log$20.warn("Initial auto-sync failed", error);
			}), TIMING.SYNC_INIT_DELAY);
			this.schedulePeriodicSync();
		}
		this.detachHistoryChanged = CoreEvents.on(AppEvents.HISTORY_CHANGED, () => {
			this.requestSync();
		});
		this.detachCollectionUpdated = CoreEvents.on(AppEvents.COLLECTION_UPDATED, () => {
			this.requestSync();
		});
		this.detachStateChanged = CoreEvents.on(AppEvents.STATE_CHANGED, ({ prop }) => {
			if (prop === "syncMode" || prop === "syncInterval") {
				if (this.dependencies.state.get("syncMode") === "none" || this.dependencies.state.get("syncInterval") === -1) this.clearInitialSyncTimer();
				this.schedulePeriodicSync();
			}
			if (prop === "syncMode" && this.dependencies.state.get("syncMode") !== "none") this.requestSync();
		});
		this.onlineHandler = () => {
			log$20.info("Network restored, requesting sync");
			this.requestSync();
		};
		window.addEventListener("online", this.onlineHandler);
	}
	stop() {
		this.clearInitialSyncTimer();
		this.clearRequestSyncTimer();
		this.clearPeriodicSyncTimer();
		this.detachHistoryChanged?.();
		this.detachHistoryChanged = null;
		this.detachCollectionUpdated?.();
		this.detachCollectionUpdated = null;
		this.detachStateChanged?.();
		this.detachStateChanged = null;
		if (this.onlineHandler) {
			window.removeEventListener("online", this.onlineHandler);
			this.onlineHandler = null;
		}
	}
	requestSync() {
		const interval = this.dependencies.state.get("syncInterval");
		const mode = this.dependencies.state.get("syncMode");
		if (interval === -1 || mode === "none") return;
		if (interval === 0) {
			Logger.info("SyncService", `Sync requested (real-time mode), executing in ${TIMING.SYNC_DEBOUNCE_MS}ms`);
			this.clearRequestSyncTimer();
			this.syncRequestTimer = setTimeout(() => this.dependencies.performSync().catch((error) => log$20.error("Debounced sync failed", error)), TIMING.SYNC_DEBOUNCE_MS);
			return;
		}
		const lastAutoSync = readLastAutoSyncTs();
		const now = Date.now();
		const minInterval = interval * 60 * 1e3;
		const elapsed = now - lastAutoSync;
		this.clearRequestSyncTimer();
		const execute = () => {
			this.dependencies.performSync().then(() => {
				this.recordAutoSyncTimestamp();
			}).catch((error) => log$20.error("Scheduled sync failed", error));
		};
		if (elapsed >= minInterval) {
			Logger.info("SyncService", `Sync requested (interval: ${interval}min), executing in ${TIMING.SYNC_DEBOUNCE_MS}ms`);
			this.syncRequestTimer = setTimeout(execute, TIMING.SYNC_DEBOUNCE_MS);
			return;
		}
		const waitMs = Math.max(0, minInterval - elapsed);
		Logger.info("SyncService", `Sync requested (interval: ${interval}min), queued for ${waitMs}ms`);
		this.syncRequestTimer = setTimeout(execute, waitMs);
	}
	clearInitialSyncTimer() {
		if (!this.initialSyncTimer) return;
		clearTimeout(this.initialSyncTimer);
		this.initialSyncTimer = null;
	}
	clearRequestSyncTimer() {
		if (!this.syncRequestTimer) return;
		clearTimeout(this.syncRequestTimer);
		this.syncRequestTimer = null;
	}
	clearPeriodicSyncTimer() {
		if (!this.periodicSyncTimer) return;
		clearTimeout(this.periodicSyncTimer);
		this.periodicSyncTimer = null;
	}
	schedulePeriodicSync() {
		this.clearPeriodicSyncTimer();
		this.clearRequestSyncTimer();
		const mode = this.dependencies.state.get("syncMode");
		const interval = this.dependencies.state.get("syncInterval");
		if (mode === "none" || interval === -1) return;
		const periodMs = interval === 0 ? TIMING.SYNC_REALTIME_POLL_MS : interval * 60 * 1e3;
		this.periodicSyncTimer = setTimeout(() => {
			this.schedulePeriodicSync();
			this.requestSync();
		}, periodMs);
	}
};
var log$19 = Logger.scope("SyncService");
var SyncService = class {
	deps;
	scheduler;
	recoveryActions;
	pendingSync = false;
	pendingSyncTimer = null;
	constructor(deps) {
		this.deps = deps;
		this.scheduler = new SyncScheduler({
			performSync: (isManual) => this.performSync(isManual),
			state: deps.state
		});
		this.recoveryActions = new SyncRecoveryActions({
			confirmAction: (message) => confirm(message),
			markAllHistoryDirty: async () => {
				await this.deps.data.db.history.toCollection().modify({ sync_dirty: 1 });
			},
			resetLastSync: () => {
				resetSyncCursor();
			},
			performSync: (isManual, forceRefresh, preferRemote) => this.performSync(isManual, forceRefresh, preferRemote),
			getPushAllMessage: () => t("alertPushAllQuery"),
			getPullAllMessage: () => t("alertPullAllQuery")
		});
	}
	onBootstrap() {
		this.scheduler.start();
	}
	onCleanup() {
		this.clearPendingSyncTimer();
		this.scheduler.stop();
	}
	getProvider() {
		return getActiveSyncProvider();
	}
	set onProgress(val) {
		getSupabaseAuthProvider().onProgress = val;
	}
	async login(email, password) {
		return await getSupabaseAuthProvider().login(email, password);
	}
	async signup(email, password) {
		return await getSupabaseAuthProvider().signup(email, password);
	}
	async logout(silent = false) {
		const provider = this.getProvider();
		if (provider) await provider.logout(silent);
		else await getSupabaseAuthProvider().logout(silent);
		this.deps.state.proxy.syncMode = "none";
		this.deps.state.proxy.syncStatus = SYNC_STATUS.IDLE;
		this.deps.state.proxy.lastSyncResult = null;
		resetSyncCursor();
	}
	async testWebDAV() {
		return await getWebDAVTestProvider().test();
	}
	requestSync() {
		this.scheduler.requestSync();
	}
	async performSync(isManual = false, forceRefresh = false, preferRemote = false) {
		await PerformanceMonitor.measure("sync.execute", async () => {
			await this.executeSync(isManual, forceRefresh, preferRemote);
		}, {
			isManual,
			forceRefresh,
			preferRemote,
			provider: this.getProvider()?.name || "none"
		});
	}
	async executeSync(isManual, forceRefresh, preferRemote) {
		const traceId = Logger.traceId;
		try {
			const provider = this.getProvider();
			if (!provider) {
				if (isManual) this.recordSyncFailure(t("syncErrorNoProvider"), true);
				return;
			}
			if (!await syncLockCoordinator.runWithLock(async () => {
				this.clearPendingSync();
				log$19.info(`Starting sync (manual: ${isManual}, force: ${forceRefresh})`, traceId);
				await provider.performSync(isManual, forceRefresh, preferRemote, traceId);
			}, isManual)) if (isManual) this.recordSyncFailure(t("syncErrorLocked"), true);
			else this.schedulePendingSyncRetry();
			else CoreEvents.emit(AppEvents.SYNC_STATUS_CHANGED, this.deps.state.proxy.syncStatus);
		} catch (error) {
			log$19.error("Sync failed", error, traceId);
			this.recordSyncFailure(error instanceof Error ? error.message : String(error), isManual);
		}
	}
	schedulePendingSyncRetry() {
		if (this.pendingSync) return;
		this.pendingSync = true;
		this.clearPendingSyncTimer();
		this.pendingSyncTimer = setTimeout(() => {
			this.pendingSyncTimer = null;
			this.pendingSync = false;
			this.performSync(false);
		}, TIMING.RETRY_DELAY_MS);
	}
	clearPendingSync() {
		this.pendingSync = false;
		this.clearPendingSyncTimer();
	}
	clearPendingSyncTimer() {
		if (!this.pendingSyncTimer) return;
		clearTimeout(this.pendingSyncTimer);
		this.pendingSyncTimer = null;
	}
	recordSyncFailure(message, notifyUser = false) {
		this.deps.state.proxy.lastSyncResult = createSyncResultSummary({
			mode: this.getCurrentMode(),
			status: SYNC_STATUS.ERROR,
			completedAt: (/* @__PURE__ */ new Date()).toISOString(),
			errorMessage: message,
			translate: t
		});
		this.deps.state.proxy.syncStatus = SYNC_STATUS.ERROR;
		if (notifyUser) CoreEvents.emit(AppEvents.SHOW_TOAST, {
			message,
			type: "error"
		});
	}
	getCurrentMode() {
		const mode = this.deps.state.proxy.syncMode;
		return mode === "webdav" || mode === "supabase" ? mode : "none";
	}
	async forceFullSync(skipConfirm = false) {
		return await this.recoveryActions.forceFullSync(skipConfirm);
	}
	async forcePullSync(skipConfirm = false) {
		return await this.recoveryActions.forcePullSync(skipConfirm);
	}
};
/** Constructed once from app/modules (composition root). */
function createSyncService(deps) {
	return new SyncService(deps);
}
var log$18 = Logger.scope("TombstoneService");
var CleanupService = class {
	deps;
	constructor(deps) {
		this.deps = deps;
	}
	RETENTION_PERIOD = 2592e6;
	maintenanceTimer = null;
	onBootstrap() {
		this.onCleanup();
		log$18.debug("Scheduling maintenance");
		this.maintenanceTimer = setTimeout(() => {
			this.maintenanceTimer = null;
			this.runAllGC().catch((e) => log$18.error("Maintenance failed", e));
		}, 5e3);
	}
	onCleanup() {
		if (this.maintenanceTimer) {
			clearTimeout(this.maintenanceTimer);
			this.maintenanceTimer = null;
		}
	}
	async runAllGC() {
		Logger.group("Cleanup", "System maintenance");
		try {
			await this.runTombstoneGC();
			await this.deps.data.runGC();
			log$18.info("Maintenance complete");
		} finally {
			Logger.groupEnd();
		}
	}
	async runTombstoneGC() {
		try {
			const thresholdDate = new Date(Date.now() - this.RETENTION_PERIOD).toISOString();
			const deletedCount = await this.deps.data.db.history.where("is_deleted").equals(1).and((item) => (item.updated_at ?? "") < thresholdDate).delete();
			if (deletedCount > 0) log$18.info(`Purged ${deletedCount} tombstone records`);
		} catch (e) {
			log$18.error("Tombstone GC failed", e);
		}
	}
};
/** Constructed once from app/modules (composition root). */
function createCleanupService(deps) {
	return new CleanupService(deps);
}
var QueryCache = class {
	cache = /* @__PURE__ */ new Map();
	version = 0;
	get(key) {
		const entry = this.cache.get(key);
		return entry && entry.version === this.version ? entry.data : null;
	}
	set(key, data) {
		this.cache.set(key, {
			data,
			version: this.version
		});
	}
	invalidate() {
		this.cache.clear();
		this.version = this.version >= Number.MAX_SAFE_INTEGER ? 0 : this.version + 1;
	}
};
var MemoryCache = class {
	cache = /* @__PURE__ */ new Map();
	get(id, expirationMs) {
		const item = this.cache.get(id);
		if (!item) return null;
		if (Date.now() - item.timestamp >= expirationMs) {
			this.cache.delete(id);
			return null;
		}
		this.cache.delete(id);
		this.cache.set(id, item);
		return item.value;
	}
	set(id, value) {
		if (this.cache.size >= CACHE.MEMORY_MAX_SIZE) {
			const firstKey = this.cache.keys().next().value;
			if (firstKey) this.cache.delete(firstKey);
		}
		this.cache.set(id, {
			value,
			timestamp: Date.now()
		});
	}
	clear() {
		this.cache.clear();
	}
	delete(id) {
		this.cache.delete(id);
	}
};
var FC2Database = class extends Dexie {
	history;
	cache;
	timeline;
	constructor() {
		super(DATABASE.NAME);
		this.version(6).stores({
			history: "&id, timestamp, status, updated_at, is_deleted, sync_dirty, retry_count, [is_deleted+timestamp], [sync_dirty+updated_at], [status+is_deleted], [retry_count+sync_dirty]",
			cache: "&id, timestamp"
		});
		this.version(8).stores({
			history: "&id, timestamp, status, updated_at, is_deleted, sync_dirty, retry_count, [is_deleted+timestamp], [sync_dirty+updated_at], [status+is_deleted], [retry_count+sync_dirty]",
			cache: "&id, timestamp",
			itemDetails: null,
			timeline: "++id, timestamp, category"
		});
		this.version(9).stores({
			history: "&id, timestamp, status, updated_at, is_deleted, sync_dirty, retry_count, [is_deleted+timestamp], [sync_dirty+updated_at], [status+is_deleted], [retry_count+sync_dirty]",
			cache: "&id, timestamp, type",
			timeline: "++id, timestamp, category"
		});
	}
};
var log$17 = Logger.scope("DataService");
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
/**
* Keeps updated_at monotonic even when the device clock lags the server.
* Local edits always get a timestamp strictly newer than the last known version.
*/
var nextUpdatedAt = (existing) => {
	const now = Date.now();
	const previous = existing?.updated_at ? new Date(existing.updated_at).getTime() : NaN;
	if (!Number.isFinite(previous) || previous < now) return nowIso();
	return new Date(previous + 1).toISOString();
};
var DataService = class {
	db = new FC2Database();
	qCache = new QueryCache();
	mCache = new MemoryCache();
	async onInit() {
		log$17.debug("Opening Dexie database");
		await this.db.open();
		log$17.info("Database opened");
	}
	onCleanup() {
		this.qCache.invalidate();
		this.mCache.clear();
		this.db.close();
	}
	history = {
		table: null,
		add: async (id, status = "watched") => {
			const key = String(id);
			const now = nextUpdatedAt(await this.db.history.get(key));
			await this.db.history.put({
				id: key,
				timestamp: Date.now(),
				status,
				updated_at: now,
				is_deleted: 0,
				sync_dirty: 1
			});
			this.qCache.invalidate();
			log$17.trace(`History added: ${id} as ${status}`);
		},
		getAll: async () => {
			const cached = this.qCache.get("all");
			if (cached) return cached;
			const items = await this.db.history.where("is_deleted").equals(0).toArray();
			this.qCache.set("all", items);
			return items;
		},
		remove: async (id) => {
			const key = String(id);
			const existing = await this.db.history.get(key);
			await this.db.history.update(key, {
				is_deleted: 1,
				updated_at: nextUpdatedAt(existing),
				sync_dirty: 1
			});
			this.qCache.invalidate();
		},
		bulkAdd: async (ids, status = "watched") => {
			const timestamp = Date.now();
			const keys = ids.map(String);
			const existingRows = await this.db.history.bulkGet(keys);
			const existingById = new Map(existingRows.filter((row) => Boolean(row)).map((row) => [row.id, row]));
			await this.db.history.bulkPut(keys.map((id) => ({
				id,
				timestamp,
				status,
				updated_at: nextUpdatedAt(existingById.get(id)),
				is_deleted: 0,
				sync_dirty: 1
			})));
			this.qCache.invalidate();
			log$17.trace(`History bulk added: ${ids.length} items as ${status}`);
		},
		bulkRemove: async (ids) => {
			await this.db.history.where("id").anyOf(ids.map(String)).modify((row) => {
				row.is_deleted = 1;
				row.updated_at = nextUpdatedAt(row);
				row.sync_dirty = 1;
			});
			this.qCache.invalidate();
			log$17.trace(`History bulk removed: ${ids.length} items`);
		},
		clear: async () => {
			await this.db.history.toCollection().modify((row) => {
				row.is_deleted = 1;
				row.sync_dirty = 1;
				row.updated_at = nextUpdatedAt(row);
			});
			this.qCache.invalidate();
		},
		markDirty: async (id) => {
			const item = await this.db.history.get(String(id));
			const currentRetry = item?.retry_count || 0;
			await this.db.history.update(String(id), {
				sync_dirty: 1,
				updated_at: nextUpdatedAt(item),
				retry_count: currentRetry + 1
			});
			this.qCache.invalidate();
		}
	};
	cache = {
		get: async (id) => {
			const memoryValue = this.mCache.get(id, CACHE.MEMORY_EXPIRATION_MS);
			if (memoryValue !== null) return memoryValue;
			const row = await this.db.cache.get(id);
			if (!row) return null;
			if (isNegativeMagnetCacheValue(row.value) ? isExpiredNegativeMagnetCacheValue(row.value) : Date.now() - row.timestamp >= CACHE.EXPIRATION_MS) {
				await this.db.cache.delete(id);
				return null;
			}
			this.mCache.set(id, row.value);
			return row.value;
		},
		set: async (id, value, type) => {
			await this.db.cache.put({
				id: String(id),
				value,
				timestamp: Date.now(),
				type
			});
			this.mCache.set(id, value);
		},
		delete: async (id) => {
			await this.db.cache.delete(String(id));
			this.mCache.delete(id);
		},
		clear: async () => {
			await this.db.cache.clear();
			this.mCache.clear();
		},
		clearByType: async (type) => {
			const keys = await this.db.cache.where("type").equals(type).primaryKeys();
			if (keys.length > 0) await this.db.cache.bulkDelete(keys);
		},
		getAll: async () => await this.db.cache.toArray()
	};
	timeline = {
		add: async (event) => {
			return await this.db.timeline.add(event);
		},
		list: async (options) => {
			let collection = this.db.timeline.orderBy("timestamp").reverse();
			if (options?.category) collection = this.db.timeline.where("category").equals(options.category).reverse();
			const limit = options?.limit ?? 100;
			return await collection.limit(limit).toArray();
		},
		purgeBefore: async (thresholdMs) => {
			return await this.db.timeline.where("timestamp").below(thresholdMs).delete();
		}
	};
	constructor() {
		this.history.table = this.db.history;
	}
	async runGC() {
		Logger.group("Database", "Garbage collection");
		const now = Date.now();
		await this.db.cache.where("timestamp").below(now - CACHE.EXPIRATION_MS).delete();
		this.mCache.clear();
		Logger.groupEnd();
	}
};
var createDataService = () => new DataService();
var Repository = createDataService();
Repository.db;
var ports = null;
function configureSitePorts(next) {
	ports = next;
}
function requirePorts() {
	if (!ports) throw new Error("[SitePorts] Not configured — registerModules must call configureSitePorts");
	return ports;
}
var fetchActressFromFD2 = (id) => requirePorts().fetchActressFromFD2(id);
var resetFD2Backoff = () => requirePorts().resetFD2Backoff();
var addToHistory = (id) => requirePorts().addToHistory(id);
var cacheSet = (key, value) => requirePorts().cacheSet(key, value);
function fromPatterns(patterns) {
	return () => {
		if (patterns.custom) return patterns.custom();
		const path = location.pathname;
		const segments = path.split("/").filter(Boolean);
		if (patterns.blacklist?.some((bl) => segments.some((s) => s.toLowerCase().startsWith(bl.toLowerCase())))) return PageContext.List;
		if (patterns.detail.some((re) => re.test(path))) return PageContext.Detail;
		if (patterns.search.some((re) => re.test(path))) return PageContext.Search;
		return PageContext.List;
	};
}
function fromUrlStructure(config) {
	return fromPatterns({
		detail: config.detailPatterns.map((p) => new RegExp(p)),
		search: config.searchPatterns.map((p) => new RegExp(p)),
		blacklist: config.blacklistPaths,
		custom: config.custom
	});
}
function fc2ContextDetector(options) {
	return fromUrlStructure({
		detailPatterns: options?.detailPatterns || ["^/articles/\\d+", "^/\\d+"],
		searchPatterns: options?.searchPatterns || ["^/search", "^/actresses"],
		blacklistPaths: options?.blacklistPaths || [
			"tags",
			"categories",
			"makers"
		]
	});
}
function javContextDetector(options) {
	return fromUrlStructure({
		detailPatterns: options?.detailPatterns || ["^/[a-z]{2,10}-\\d+", "^/video/\\d+"],
		searchPatterns: options?.searchPatterns || ["^/search"],
		blacklistPaths: options?.blacklistPaths || [
			"categories",
			"tags",
			"makers",
			"series"
		]
	});
}
function hybridContextDetector(options) {
	return fromUrlStructure({
		detailPatterns: options?.detailPatterns || [
			"^/articles/\\d+",
			"^/[a-z]{2,10}-\\d+",
			"^/fc2-ppv-\\d+"
		],
		searchPatterns: options?.searchPatterns || ["^/search", "^/actresses"],
		blacklistPaths: options?.blacklistPaths || [
			"tags",
			"categories",
			"makers",
			"genres"
		]
	});
}
function extractFC2Id(text) {
	if (!text) return null;
	for (const pattern of [
		/FC2-PPV-(\d+)/i,
		/FC2PPV(\d+)/i,
		/(\d{5,8})/
	]) {
		const match = text.match(pattern);
		if (match?.[1]) return match[1];
	}
	return null;
}
function extractJavCode(text) {
	if (!text) return null;
	return text.match(/([a-z]{2,10}-\d+)/i)?.[1]?.toUpperCase() || null;
}
function detectVideoType(id) {
	if (!id) return "jav";
	if (/^\d{5,8}$/.test(id) || id.toUpperCase().startsWith("FC2")) return "fc2";
	return "jav";
}
function cleanTitle(title, id) {
	if (!title) return `Video-${id}`;
	const cleaned = title.replace(new RegExp(`(?:FC2-PPV-)?${id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*`, "gi"), "").replace(/FC2-PPV-\d+\s*/gi, "").replace(/\s+/g, " ").replace(/^\s*[-–—]\s*/, "").replace(/\s*[-–—]\s*$/, "").trim();
	if (!cleaned) return title.trim() || `Video-${id}`;
	return cleaned;
}
function extractTitle(title) {
	if (!title) return "";
	return title.replace(/\s+/g, " ").replace(/^\s*[-–—]\s*/, "").replace(/\s*[-–—]\s*$/, "").trim();
}
function resolveImageUrl$1(img) {
	if (!img) return void 0;
	return getBestImageSource(img) || void 0;
}
function normalizeUrl(url) {
	if (!url) return void 0;
	try {
		if (url.startsWith("/")) return url;
		const u = new URL(url, location.origin);
		return u.origin + u.pathname + u.search;
	} catch {
		return url;
	}
}
function normalizeOptionalText(text) {
	if (!text) return void 0;
	const cleaned = text.trim();
	return cleaned.length > 0 ? cleaned : void 0;
}
function normalizeTags(tags) {
	return tags.filter((tag) => Boolean(tag)).map((tag) => tag.trim()).filter((tag) => tag.length > 0);
}
function resolvePreviewSlug(type, id, fallback) {
	if (fallback) return fallback;
	if (type === "fc2") return `fc2-ppv-${id}`;
}
function safeQuerySelector(container, selector) {
	try {
		return container.querySelector(selector);
	} catch {
		return null;
	}
}
function safeQuerySelectorAll(container, selector) {
	try {
		return Array.from(container.querySelectorAll(selector));
	} catch {
		return [];
	}
}
function extractTextContent(element, selector) {
	if (!element) return void 0;
	return normalizeOptionalText((selector ? safeQuerySelector(element, selector) : element)?.textContent);
}
function createCardExtractor(config) {
	return (card) => {
		if (config.shouldSkip?.(card)) return null;
		const link = safeQuerySelector(card, config.linkSelector);
		if (!link?.href) return null;
		const id = config.idExtractor(link, card);
		if (!id) return null;
		const type = detectVideoType(id);
		const imageUrl = resolveImageUrl$1(safeQuerySelector(card, config.imageSelector || "img"));
		const title = extractTitle((config.titleSelector ? safeQuerySelector(card, config.titleSelector) : link)?.textContent?.trim() || "");
		const actress = config.actressSelector ? extractTextContent(card, config.actressSelector) : void 0;
		const tags = config.tagsSelector ? normalizeTags(safeQuerySelectorAll(card, config.tagsSelector).map((el) => el.textContent)) : [];
		const previewSlug = config.previewSlugResolver ? config.previewSlugResolver(id, type) : resolvePreviewSlug(type, id);
		const baseData = {
			id,
			type,
			title,
			primaryImageUrl: imageUrl,
			imageUrl,
			fallbackImageUrl: imageUrl,
			articleUrl: normalizeUrl(link.href),
			actress,
			tags,
			previewSlug: previewSlug || void 0
		};
		if (config.customExtractor) return config.customExtractor(card, baseData);
		return baseData;
	};
}
function createFC2CardExtractor(config) {
	return createCardExtractor({
		...config,
		idExtractor: (link, card) => {
			let id = extractFC2Id(link.href);
			if (!id) id = extractFC2Id(card.textContent || "");
			return id;
		},
		previewSlugResolver: (id) => resolvePreviewSlug("fc2", id, `fc2-ppv-${id}`)
	});
}
function createJAVCardExtractor(config) {
	return createCardExtractor({
		...config,
		idExtractor: (link, card) => {
			let id = extractJavCode(link.textContent || "");
			if (!id) id = extractJavCode(link.href);
			if (!id) id = extractJavCode(card.textContent || "");
			return id;
		}
	});
}
function createHybridCardExtractor(config) {
	return createCardExtractor({
		...config,
		idExtractor: (link, card) => {
			let id = extractFC2Id(link.href) || extractFC2Id(card.textContent || "");
			if (!id) id = extractJavCode(link.textContent || "") || extractJavCode(link.href);
			return id;
		},
		previewSlugResolver: (id, type) => {
			if (type === "fc2") return `fc2-ppv-${id}`;
		}
	});
}
function createDetailEnhancer(config) {
	return (container) => {
		const info = config.idExtractor();
		if (!info) return null;
		return {
			mode: config.mountMode || "card",
			anchorSelector: config.anchorSelector,
			anchorElement: config.anchorElement || container,
			idExtractor: () => info,
			metadataExtractor: (id, type) => {
				return {
					...config.metadataExtractor(id, type),
					articleUrl: location.href
				};
			},
			mountOptions: config.mountOptions,
			onMounted: config.onMounted
		};
	};
}
function createFC2DetailEnhancer(config) {
	return createDetailEnhancer({
		idExtractor: () => {
			const id = extractFC2Id(location.href);
			return id ? {
				id,
				type: "fc2"
			} : null;
		},
		metadataExtractor: (id) => {
			const title = document.querySelector(config.titleSelector || "h1")?.textContent?.trim() || document.title.split("-")[0]?.trim();
			const img = document.querySelector(config.imageSelector || "img");
			return {
				title: cleanTitle(title || "", id),
				primaryImageUrl: resolveImageUrl$1(img)
			};
		},
		mountMode: config.mountMode,
		anchorSelector: config.anchorSelector,
		mountOptions: config.mountOptions,
		onMounted: config.onMounted
	});
}
function createJAVDetailEnhancer(config) {
	return createDetailEnhancer({
		idExtractor: () => {
			return MediaUtils.parseVideoId(document.title, location.href) || null;
		},
		metadataExtractor: (_id, _type) => {
			return {
				title: extractTextContent(document.querySelector(config.titleSelector || "h1")) || document.title,
				actress: config.actressSelector ? extractTextContent(document.querySelector(config.actressSelector)) : void 0
			};
		},
		mountMode: config.mountMode,
		anchorSelector: config.anchorSelector,
		mountOptions: config.mountOptions,
		onMounted: config.onMounted
	});
}
function createDefaultMeta(overrides = {}) {
	return {
		name: "Unknown",
		hostnames: [],
		listOnDetail: true,
		type: "hybrid",
		enabled: true,
		priority: 100,
		features: [
			"magnet",
			"preview",
			"history"
		],
		...overrides
	};
}
function validateSiteMeta(meta) {
	const errors = [];
	if (!meta.name || meta.name.trim() === "") errors.push("Site name is required");
	if (!meta.hostnames || meta.hostnames.length === 0) errors.push("At least one hostname is required");
	if (![
		"fc2",
		"jav",
		"hybrid"
	].includes(meta.type)) errors.push(`Invalid site type: ${meta.type}`);
	if (meta.priority !== void 0 && (meta.priority < 0 || meta.priority > 1e3)) errors.push("Priority must be between 0 and 1000");
	return {
		valid: errors.length === 0,
		errors
	};
}
var AdapterBuilder = class {
	_meta = {};
	_contextDetector;
	_cardExtractor;
	_detailEnhancer;
	_postProcessor;
	_identityKey;
	_shouldProcessCard;
	_containerSelector;
	_cardSelector;
	_getExtraUi;
	_triggerSelector;
	_mainImageSelector;
	withMeta(meta) {
		this._meta = {
			...this._meta,
			...meta
		};
		return this;
	}
	withContextDetector(detector) {
		this._contextDetector = detector;
		return this;
	}
	withFC2Context(options) {
		this._contextDetector = fc2ContextDetector(options);
		return this;
	}
	withJAVContext(options) {
		this._contextDetector = javContextDetector(options);
		return this;
	}
	withHybridContext(options) {
		this._contextDetector = hybridContextDetector(options);
		return this;
	}
	withCardExtractor(extractor) {
		this._cardExtractor = extractor;
		return this;
	}
	withFC2CardExtractor(config) {
		this._cardExtractor = createFC2CardExtractor(config);
		return this;
	}
	withJAVCardExtractor(config) {
		this._cardExtractor = createJAVCardExtractor(config);
		return this;
	}
	withHybridCardExtractor(config) {
		this._cardExtractor = createHybridCardExtractor(config);
		return this;
	}
	withDetailEnhancer(enhancer) {
		this._detailEnhancer = enhancer;
		return this;
	}
	withFC2DetailEnhancer(config) {
		this._detailEnhancer = createFC2DetailEnhancer(config);
		return this;
	}
	withJAVDetailEnhancer(config) {
		this._detailEnhancer = createJAVDetailEnhancer(config);
		return this;
	}
	withPostProcessor(processor) {
		this._postProcessor = processor;
		return this;
	}
	withIdentityKey(extractor) {
		this._identityKey = extractor;
		return this;
	}
	withShouldProcessCard(checker) {
		this._shouldProcessCard = checker;
		return this;
	}
	withContainerSelector(selector) {
		this._containerSelector = selector;
		return this;
	}
	withCardSelector(selector) {
		this._cardSelector = selector;
		return this;
	}
	withExtraUi(extractor) {
		this._getExtraUi = extractor;
		return this;
	}
	withTriggerSelector(selector) {
		this._triggerSelector = selector;
		return this;
	}
	withMainImageSelector(selector) {
		this._mainImageSelector = selector;
		return this;
	}
	build() {
		const meta = createDefaultMeta(this._meta);
		const validation = validateSiteMeta(meta);
		if (!validation.valid) throw new Error(`Invalid site meta: ${validation.errors.join(", ")}`);
		if (!this._contextDetector) switch (meta.type) {
			case "fc2":
				this._contextDetector = fc2ContextDetector();
				break;
			case "jav":
				this._contextDetector = javContextDetector();
				break;
			default: this._contextDetector = hybridContextDetector();
		}
		const config = {
			name: meta.name,
			hostnames: meta.hostnames,
			listOnDetail: meta.listOnDetail,
			detectContext: this._contextDetector
		};
		if (this._cardExtractor) config.list = {
			containerSelector: this._containerSelector || this.getDefaultContainerSelector(meta.type),
			cardSelector: this._cardSelector || this.getDefaultCardSelector(),
			identityKey: this._identityKey || this.getDefaultIdentityKey(),
			extractor: this._cardExtractor,
			shouldProcessCard: this._shouldProcessCard,
			postProcess: this._postProcessor,
			getExtraUi: this._getExtraUi
		};
		if (this._detailEnhancer) config.detail = {
			customDetailAction: this._detailEnhancer,
			triggerSelector: this._triggerSelector,
			mainImageSelector: this._mainImageSelector
		};
		return config;
	}
	getDefaultContainerSelector(type) {
		switch (type) {
			case "fc2": return ".movie-list, .flex.flex-wrap, main";
			case "jav": return ".movie-list, .grid, main";
			default: return "main, .grid, .flex.flex-wrap";
		}
	}
	getDefaultCardSelector() {
		return ".item:not(.fc2-card-rebuilt), div:not(.fc2-card-rebuilt)";
	}
	getDefaultIdentityKey() {
		return (card) => {
			const link = card.querySelector("a");
			if (!link?.href) return null;
			try {
				return new URL(link.href, location.origin).pathname.toLowerCase();
			} catch {
				return link.href.toLowerCase();
			}
		};
	}
};
/**
*
* ```typescript
* import { fc2Site } from '../sites/core';
*
* export const mysite = fc2Site({
*   name: 'MySite',
*   hostnames: ['mysite.com'],
*   linkSelector: 'a[href*="/video/"]',
*   titleSelector: '.video-title',
*   imageSelector: '.video-thumb img'
* });
* ```
*
* ```typescript
* import { AdapterBuilder } from '../sites/core';
*
* export const customsite = new AdapterBuilder()
*   .withMeta({ name: 'Custom', hostnames: ['custom.com'], type: 'hybrid' })
*   .withHybridContext()
*   .withHybridCardExtractor({ linkSelector: 'a.link' })
*   .build();
* ```
*
* ```typescript
* import { extractFC2Id, cleanTitle, resolveImageUrl } from '../sites/core';
*
* const id = extractFC2Id('FC2-PPV-12345'); // '12345'
* const title = cleanTitle('FC2-PPV-12345 - Title', '12345'); // 'Title'
* ```
*/
/**
* Fetch actress name from FD2 and mount an actress button if found.
* Shared across site adapters that support actress display on detail pages.
*
* @returns The fetched actress name, or null if not found
*/
var fetchAndMountActress = async (id, containers, api) => {
	const fetchedActress = await fetchActressFromFD2(id);
	if (fetchedActress && containers.linksContainer) api.addActressButton(containers.linksContainer, fetchedActress);
	return fetchedActress ?? null;
};
/**
* Cache an actress name for a given video ID.
* Shared across site adapters that extract actress from the page.
*/
var cacheActressName = (id, actress) => {
	if (actress) cacheSet(`actress_${id}`, actress);
};
/**
*
*
* Domain: fc2cmadb.com
* Stack: Laravel + Livewire + DaisyUI (Tailwind CSS)
* Language: Japanese (ja)
*/
var SITE_NAME = "FC2CMADB";
var CARD_CONTAINER_SELECTOR = ".flex.flex-wrap.bg-base-100.p-2";
var CARD_SELECTOR = ".card.bg-base-100.card-sm:not(.fc2-card-rebuilt)";
var DETAIL_IMAGE_SELECTOR = ".hero-content .bg-base-200";
var DETAIL_TITLE_SELECTOR = "h1.text-xl.font-bold";
var ARTICLE_LINK_SELECTOR = "a[href*=\"/articles/\"]";
var PENDING_RETRY_ATTR = "data-fc2cmadb-retry";
var resolveImageUrl = (card) => {
	const img = card.querySelector("figure img");
	const dataSrc = img?.dataset.original || img?.dataset.src || img?.dataset.lazySrc;
	if (dataSrc) return dataSrc;
	const src = img?.src;
	if (src && !src.startsWith("data:") && !src.includes("pixel.gif")) return src;
	const srcset = img?.srcset;
	if (srcset) {
		const firstSrc = srcset.split(",")[0]?.trim().split(" ")[0];
		if (firstSrc) return firstSrc;
	}
	return "";
};
var scheduleImageRetry = (card) => {
	if (card.hasAttribute(PENDING_RETRY_ATTR)) return;
	card.setAttribute(PENDING_RETRY_ATTR, "1");
	let done = false;
	let observer = null;
	let timeout = void 0;
	let trackedImg = null;
	let onTrackedImgLoad = null;
	const detachImgListener = () => {
		if (trackedImg && onTrackedImgLoad) trackedImg.removeEventListener("load", onTrackedImgLoad);
		onTrackedImgLoad = null;
	};
	const retry = () => {
		if (done || !resolveImageUrl(card)) return;
		done = true;
		observer?.disconnect();
		if (timeout !== void 0) clearTimeout(timeout);
		detachImgListener();
		card.removeAttribute(PENDING_RETRY_ATTR);
		const parent = card.parentElement;
		const next = card.nextSibling;
		if (parent) {
			parent.removeChild(card);
			if (next) parent.insertBefore(card, next);
			else parent.appendChild(card);
		}
	};
	const syncImgListener = () => {
		const currentImg = card.querySelector("figure img");
		if (currentImg === trackedImg) return;
		detachImgListener();
		trackedImg = currentImg;
		if (trackedImg) {
			onTrackedImgLoad = retry;
			trackedImg.addEventListener("load", onTrackedImgLoad);
		}
	};
	syncImgListener();
	observer = new MutationObserver(() => {
		syncImgListener();
		retry();
	});
	observer.observe(card, {
		childList: true,
		subtree: true,
		attributes: true,
		attributeFilter: ["src", "srcset"]
	});
	if (resolveImageUrl(card)) setTimeout(retry, 50);
	timeout = setTimeout(() => {
		if (done) return;
		done = true;
		observer?.disconnect();
		detachImgListener();
		card.removeAttribute(PENDING_RETRY_ATTR);
	}, 8e3);
};
var fc2cmadb = new AdapterBuilder().withMeta({
	name: SITE_NAME,
	hostnames: ["fc2cmadb.com"],
	type: "fc2",
	listOnDetail: true
}).withContextDetector(() => {
	const path = location.pathname;
	if (/^\/articles\/\d+/.test(path)) return PageContext.Detail;
	if (/^\/search/.test(path)) return PageContext.Search;
	return PageContext.List;
}).withCardExtractor((card) => {
	if (!card.querySelector(ARTICLE_LINK_SELECTOR)) return null;
	const link = card.querySelector(ARTICLE_LINK_SELECTOR);
	const id = MediaUtils.extractFC2Id(link?.href || "");
	if (!id) return null;
	const img = card.querySelector("figure img");
	const imageUrl = resolveImageUrl(card);
	if (!imageUrl) {
		scheduleImageRetry(card);
		return null;
	}
	const titleEl = card.querySelector(".card-body h2.card-title a");
	return {
		id,
		type: "fc2",
		title: titleEl?.title || titleEl?.textContent?.trim() || img?.alt || `FC2-PPV-${id}`,
		primaryImageUrl: imageUrl,
		imageUrl,
		fallbackImageUrl: imageUrl,
		articleUrl: link?.href || `/articles/${id}`,
		previewSlug: `fc2-ppv-${id}`
	};
}).withDetailEnhancer((cont) => {
	const buildConfig = () => ({
		mode: "card",
		anchorElement: cont,
		idExtractor: () => {
			const id = MediaUtils.extractFC2Id(location.href);
			return id ? {
				id,
				type: "fc2"
			} : null;
		},
		metadataExtractor: (id) => {
			const title = (document.querySelector(DETAIL_TITLE_SELECTOR)?.textContent || document.title.split("-")[0] || `FC2-PPV-${id}`).trim();
			const fallbackImageUrl = getBestImageSource(cont.querySelector("img")) || void 0;
			return {
				title,
				primaryImageUrl: fallbackImageUrl,
				imageUrl: fallbackImageUrl,
				fallbackImageUrl
			};
		},
		mountOptions: {
			primaryActionsOverride: [
				"id",
				"viewed",
				"magnet"
			],
			width: "",
			maxWidth: "100%"
		},
		onMounted: async (id, type, containers, api) => {
			cacheActressName(id, MediaUtils.cleanActressName(document.querySelector("[href*=\"/actresses/\"]")?.textContent));
			if (type === "fc2") await fetchAndMountActress(id, containers, api);
		}
	});
	if (cont.querySelector("img")) return buildConfig();
	return new Promise((resolve) => {
		const observer = new MutationObserver(() => {
			if (cont.querySelector("img")) {
				observer.disconnect();
				clearTimeout(timeout);
				resolve(buildConfig());
			}
		});
		observer.observe(cont, {
			childList: true,
			subtree: true
		});
		const timeout = setTimeout(() => {
			observer.disconnect();
			resolve(null);
		}, 8e3);
	});
}).withContainerSelector(CARD_CONTAINER_SELECTOR).withCardSelector(CARD_SELECTOR).withMainImageSelector(DETAIL_IMAGE_SELECTOR).withExtraUi((card) => {
	const badges = card.querySelectorAll("span.absolute.bottom-0.left-0, span.absolute.bottom-0.right-0");
	if (badges.length === 0) return {};
	return { preservedIconsHTML: Array.from(badges).map((b) => b.cloneNode(true)).map((n) => n.outerHTML).join("") };
}).withPostProcessor((card, _el, newCard) => {
	const figure = newCard?.querySelector("figure");
	const mosaicBadge = card.querySelector("span.absolute.bottom-0.left-0");
	if (mosaicBadge && figure) figure.appendChild(mosaicBadge.cloneNode(true));
	const discountBadge = card.querySelector("span.absolute.bottom-0.right-0");
	if (discountBadge && figure) figure.appendChild(discountBadge.cloneNode(true));
}).build();
var fc2db = new AdapterBuilder().withMeta({
	name: "FC2DB",
	hostnames: ["fc2db.net"],
	type: "fc2",
	listOnDetail: true
}).withContextDetector(() => {
	if (window.location.pathname.includes("/work/")) return PageContext.Detail;
	return PageContext.List;
}).withFC2CardExtractor({
	linkSelector: "a[href*=\"/work/\"]",
	titleSelector: ".mt-1.font-bold, .rec-carousel__title, h3 a, h3",
	imageSelector: "img.wp-post-image, img.rec-carousel__img, img",
	actressSelector: ".truncate"
}).withFC2DetailEnhancer({ imageSelector: ".grid > div:first-child > a[href*=\"fc2.com/article/\"] img" }).withMainImageSelector(".grid > div:first-child > a[href*=\"fc2.com/article/\"] img").withContainerSelector(".grid.gap-4:not([class*=\"minmax\"]), #rec-carousel").withCardSelector(".bg-card:not(.fc2-card-rebuilt), .rec-carousel__slide:not(.fc2-card-rebuilt)").build();
var FD2PPV_PRESERVED_BADGES = ".float i, .float .icon, .badges span, span.absolute.top-0.right-0, .artist-tags a";
var extractActresses = (card) => {
	return Array.from(card.querySelectorAll(".artist-avatar-container a")).map((a) => {
		const img = a.querySelector("img");
		const nameRaw = a.querySelector("h4")?.textContent || img?.getAttribute("alt") || img?.title || "";
		const name = MediaUtils.cleanActressName(nameRaw);
		if (!name) return null;
		return {
			name,
			avatarUrl: img?.src || void 0,
			url: a.href || void 0
		};
	}).filter((a) => a !== null);
};
var extractTags = (card) => {
	const tags = [];
	card.querySelectorAll(".float i, .float-icons i, .badges i").forEach((icon) => {
		const title = icon.getAttribute("title");
		if (title && !tags.includes(title)) tags.push(title);
	});
	card.querySelectorAll(".artist-tags a").forEach((tag) => {
		const text = tag.textContent?.trim();
		if (text && !tags.includes(text)) tags.push(text);
	});
	return tags;
};
var fd2ppv = new AdapterBuilder().withMeta({
	name: "FD2PPV",
	hostnames: ["fd2ppv.cc"],
	type: "fc2",
	listOnDetail: true
}).withContextDetector(() => {
	const segments = window.location.pathname.split("/").filter(Boolean);
	if (segments[0] === "articles" && segments.length >= 2 && segments[1] && /^\d+$/.test(segments[1])) return PageContext.Detail;
	return PageContext.List;
}).withCardExtractor((card) => {
	const link = card.querySelector("a[href*=\"/articles/\"]");
	const id = MediaUtils.extractFC2Id(link?.href || "");
	if (!id) return null;
	const carouselImages = Array.from(card.querySelectorAll(".carousel-slide img"));
	let imageUrl = "";
	if (carouselImages.length > 0) imageUrl = getBestImageSource(carouselImages.find((img) => img.dataset.src?.includes("ppvdatabank.com") || img.src?.includes("ps1.webp")) || carouselImages[0] || null);
	if (!imageUrl) imageUrl = getBestImageSource(card.querySelector("img.other-work-image") || card.querySelector("img"));
	const isActressPage = !!document.querySelector(".artist-detail-name");
	const pageActressName = isActressPage ? MediaUtils.cleanActressName(document.querySelector(".artist-detail-name")?.textContent) : null;
	const actresses = isActressPage ? [] : extractActresses(card);
	const firstActress = actresses[0];
	const actress = pageActressName || firstActress?.name || null;
	const title = card.querySelector(".artist-content a")?.textContent?.trim() || card.querySelector(".other-work-title a")?.textContent?.trim() || card.querySelector("p a")?.textContent?.trim() || card.querySelector("p")?.textContent?.trim() || card.querySelector("h3 a")?.textContent?.trim() || `FC2-PPV-${id}`;
	const tags = extractTags(card);
	return {
		id,
		type: "fc2",
		site: "FD2PPV",
		title,
		actress: actress || void 0,
		actresses,
		actressAvatarUrl: firstActress?.avatarUrl,
		actressUrl: firstActress?.url,
		primaryImageUrl: imageUrl,
		imageUrl,
		fallbackImageUrl: imageUrl,
		tags: tags.length > 0 ? tags : void 0,
		articleUrl: link?.href || `/articles/${id}`,
		previewSlug: `fc2-ppv-${id}`
	};
}).withDetailEnhancer((cont) => ({
	mode: "card",
	anchorElement: cont,
	idExtractor: () => {
		const id = MediaUtils.extractFC2Id(location.href) || MediaUtils.extractFC2Id(document.querySelector(".work-title")?.textContent || "") || MediaUtils.parseVideoId(document.querySelector(".work-meta-value a[href*=\"article\"]")?.getAttribute("href") || "")?.id;
		return id ? {
			id,
			type: "fc2"
		} : null;
	},
	metadataExtractor: (id) => {
		const fc2Pattern = new RegExp(`(?:FC2-PPV-)?0*${id}`, "i");
		const title = (document.querySelector(".work-brief")?.textContent || document.querySelector(".work-title")?.textContent || document.title.split("|")[0] || `FC2-PPV-${id}`).replace(fc2Pattern, "").trim() || `FC2-PPV-${id}`;
		const actressRaw = document.querySelector(".artist-info-card .artist-name a")?.textContent || document.querySelector(".artist-name a")?.textContent || Array.from(document.querySelectorAll(".work-meta-label")).find((el) => el.textContent?.trim() === PATTERNS.FD2PPV_SELLER_MARKER)?.nextElementSibling?.querySelector("a")?.textContent;
		const actress = MediaUtils.cleanActressName(actressRaw);
		const primaryImageUrl = getBestImageSource(cont.querySelector("img")) || void 0;
		return {
			title,
			actress: actress ?? void 0,
			primaryImageUrl,
			imageUrl: primaryImageUrl,
			fallbackImageUrl: primaryImageUrl,
			articleUrl: location.href
		};
	},
	mountOptions: { width: "" },
	onMounted: (id) => {
		const actressRaw = document.querySelector(".artist-info-card .artist-name a")?.textContent || document.querySelector(".artist-name a")?.textContent || Array.from(document.querySelectorAll(".work-meta-label")).find((el) => el.textContent?.trim() === PATTERNS.FD2PPV_SELLER_MARKER)?.nextElementSibling?.querySelector("a")?.textContent;
		const actress = MediaUtils.cleanActressName(actressRaw);
		if (actress) cacheSet(`actress_${id}`, actress);
	}
})).withContainerSelector("#artist-list, .artist-list, .work-list, .flex.flex-wrap:not(.flex-end):not(.flex-between), .container .grid, .other-works-grid").withCardSelector(".artist-card:not(.fc2-card-rebuilt), .work-card:not(.fc2-card-rebuilt), .work-list > div:not(.fc2-card-rebuilt)").withPostProcessor((card, _el, newCard, _data, classes) => {
	const stats = card.querySelector(".stats");
	if (stats && newCard && classes) {
		const infoArea = newCard.querySelector(`.${classes.infoArea}`);
		if (infoArea) infoArea.insertBefore(stats.cloneNode(true), infoArea.querySelector(`.${classes.cardActionRow}`));
	}
}).withExtraUi((card) => {
	const badges = card.querySelectorAll(FD2PPV_PRESERVED_BADGES);
	if (badges.length === 0) return {};
	return { preservedIconsHTML: Array.from(badges).map((b) => b.cloneNode(true)).map((n) => n.outerHTML).join("") };
}).withMainImageSelector(".work-image-large").build();
fd2ppv.onInit = () => {
	resetFD2Backoff();
};
var getJavdbMagnetLinks = () => {
	return Array.from(document.querySelectorAll("#magnets-content a[href^=\"magnet:?\"]"));
};
var bindJavdbNativeHistoryButton = (selector, id) => {
	const btn = document.querySelector(selector);
	if (btn && !btn.hasAttribute("data-hooked")) {
		btn.setAttribute("data-hooked", "true");
		btn.addEventListener("click", () => {
			fireAndForget(Promise.resolve(addToHistory(id)), "JavDB");
		});
	}
};
var backfillJavdbActressForFc2 = async (id, actress, finalElement, api) => {
	if (actress) return;
	const fetchedActress = await fetchActressFromFD2(id);
	if (!fetchedActress) return;
	api.addActressButton(finalElement, fetchedActress);
};
var attachJavdbNativeMagnets = async (id, type, magnetContainer, linksContainer, api) => {
	const magnetLinks = getJavdbMagnetLinks();
	const firstLink = magnetLinks[0];
	if (magnetLinks.length > 0 && firstLink) {
		cacheSet(id, firstLink.href);
		api.addMagnetButton(magnetContainer, firstLink.href);
		magnetLinks.slice(1, 3).forEach((link) => {
			api.addMagnetButton(linksContainer, link.href);
		});
		return;
	}
	await api.attachFetchedMagnet?.(id, type, magnetContainer);
};
var javdb = new AdapterBuilder().withMeta({
	name: "JavDB",
	hostnames: ["javdb.com"],
	type: "jav",
	listOnDetail: true
}).withContextDetector(() => {
	const path = location.pathname;
	if (path.includes("/search/") || location.search.includes("q=")) return PageContext.Search;
	if (path.startsWith("/v/")) return PageContext.Detail;
	return PageContext.List;
}).withCardExtractor((card) => {
	const link = card.matches("a.box, a.tile-item, a[href^=\"/v/\"]") ? card : card.querySelector("a.box, a.tile-item, a[href^=\"/v/\"]");
	if (!link) return null;
	const idStrong = card.querySelector(".video-title strong, .video-number");
	const idText = idStrong ? idStrong.textContent : link.title || link.innerText;
	const titleEl = card.querySelector(".video-title");
	let titleText;
	if (titleEl) {
		const clone = titleEl.cloneNode(true);
		clone.querySelector("strong")?.remove();
		titleText = clone.textContent?.trim() || "";
	} else titleText = link.title || link.innerText;
	const img = card.querySelector("img");
	const info = MediaUtils.parseVideoId(idText || "", link.href);
	if (!info) return null;
	const tagsContainer = card.querySelector(".tags");
	const tags = tagsContainer ? Array.from(tagsContainer.querySelectorAll(".tag")).map((t) => t.textContent?.trim()).filter(Boolean) : [];
	return {
		...info,
		title: titleText || info.id,
		primaryImageUrl: img?.src,
		imageUrl: img?.src,
		fallbackImageUrl: img?.src,
		articleUrl: link.href,
		previewSlug: info.previewSlug,
		tags
	};
}).withDetailEnhancer((cont) => ({
	mode: "card",
	anchorElement: cont,
	idExtractor: () => {
		const titleEl = document.querySelector("h2.title");
		return MediaUtils.parseVideoId(titleEl?.textContent || "", location.href);
	},
	metadataExtractor: (id, type) => {
		const titleEl = document.querySelector("h2.title");
		const img = cont.querySelector("img.video-cover");
		return {
			id,
			type,
			title: titleEl?.textContent?.trim() || "",
			primaryImageUrl: img?.src || "",
			articleUrl: location.href
		};
	},
	skipDefaultServices: true,
	onMounted: async (id, type, containers, api) => {
		const actress = MediaUtils.cleanActressName(cont.querySelector(".meta")?.textContent);
		api.addPreviewButton(containers.previewContainer, id);
		await attachJavdbNativeMagnets(id, type, containers.magnetContainer, containers.linksContainer, api);
		if (type === "fc2") await backfillJavdbActressForFc2(id, actress, containers.mainElement, api);
		bindJavdbNativeHistoryButton("form.button_to[action*=\"/reviews/watched\"] button", id);
		bindJavdbNativeHistoryButton("button.js-watched-video", id);
	},
	mountOptions: {
		maxWidth: "100%",
		width: ""
	}
})).withMainImageSelector(".column-video-cover").withContainerSelector(".movie-list, .tile-images:not(.preview-images)").withCardSelector(".item:not(.fc2-card-rebuilt), .tile-item:not(.fc2-card-rebuilt)").withPostProcessor((card, _el, newCard, _data, classes) => {
	const score = card.querySelector(".score");
	const meta = card.querySelector(".meta");
	const tags = card.querySelector(".tags");
	if (newCard && classes) {
		const infoArea = newCard.querySelector(`.${classes.infoArea}`);
		if (infoArea) {
			const actionRow = infoArea.querySelector(`.${classes.cardActionRow}`);
			if (score) infoArea.insertBefore(score.cloneNode(true), actionRow);
			if (meta) infoArea.insertBefore(meta.cloneNode(true), actionRow);
			if (tags) infoArea.insertBefore(tags.cloneNode(true), actionRow);
		}
	}
}).build();
var JAVFC2_PRESERVED_BADGES = ".video_quality span";
var JAVFC2_CONTAINER_SELECTORS = [
	"#movie-details .row",
	"#movie-details > div > div",
	".movie-container",
	".owl-stage",
	".movie-list-container .row",
	".movie-list-container > div > div",
	".similler-movie .row",
	".similler-movie > div > div",
	".similar-movie .row",
	".similar-movie > div > div",
	"#main-content .row",
	".main-content .row",
	"#hot .row",
	"#hot > div > div",
	"#section-opt .row",
	"#section-opt > div > div",
	"#latest-all .row",
	"#latest-all > div > div",
	"#latest-all-all .row",
	"#latest-all-all > div > div",
	"div[id^=\"section-\"] .row",
	"div[id^=\"section-\"] > div > div",
	"div[id^=\"latest-\"] .row",
	"div[id^=\"latest-\"] > div > div",
	".col-md-9 .row",
	".col-md-12 .row"
].join(", ");
var JAVFC2_CARD_SELECTORS = [
	".latest-movie-img-container",
	".movie-img",
	".item"
].join(", ");
var JAVFC2_IMAGE_TIMEOUT = 2e3;
var waitForCardImage = (img) => {
	if (!img) return "";
	const resolveSrc = () => {
		const src = getBestImageSource(img);
		if (src && !src.startsWith("data:") && !src.includes("pixel.gif")) return src;
		return "";
	};
	const immediate = resolveSrc();
	if (immediate) return immediate;
	setTimeout(() => resolveSrc(), JAVFC2_IMAGE_TIMEOUT);
	if (img.complete) return img.src || "";
	img.addEventListener("load", () => resolveSrc(), { once: true });
	return img.src || "";
};
var javfc2 = new AdapterBuilder().withMeta({
	name: "JavFC2",
	hostnames: ["javfc2.xyz"],
	type: "hybrid",
	listOnDetail: true
}).withContextDetector(() => {
	if (window.location.pathname.includes("/watch/")) return PageContext.Detail;
	return PageContext.List;
}).withCardExtractor((card) => {
	const titleLink = card.querySelector(".movie-title a, h3 a, a[href*=\"/watch/\"]");
	const img = card.querySelector("img");
	const altText = img?.getAttribute("alt");
	const linkText = titleLink?.textContent;
	const rawTitleText = altText && altText.length > (linkText?.length || 0) ? altText : linkText || altText;
	const href = titleLink?.getAttribute("href") || card.querySelector("a")?.getAttribute("href") || "";
	const videoInfo = MediaUtils.parseVideoId(rawTitleText || "", href);
	if (!videoInfo) return null;
	const imageUrl = waitForCardImage(img);
	return {
		...videoInfo,
		site: "JavFC2",
		title: rawTitleText || `${videoInfo.type.toUpperCase()}-${videoInfo.id}`,
		primaryImageUrl: imageUrl,
		imageUrl,
		fallbackImageUrl: imageUrl,
		articleUrl: href,
		previewSlug: videoInfo.previewSlug
	};
}).withDetailEnhancer((playerDiv) => {
	const titleText = document.querySelector(".ml-title span.title")?.textContent || document.querySelector("h1")?.textContent || document.title;
	const videoInfo = MediaUtils.parseVideoId(titleText, location.href);
	if (!videoInfo) return null;
	return {
		mode: "toolbar",
		anchorElement: playerDiv,
		idExtractor: () => videoInfo,
		metadataExtractor: () => {
			const actressRaw = document.querySelector(".col-md-9 a[href*=\"/star/\"]")?.textContent;
			const actress = MediaUtils.cleanActressName(actressRaw);
			return {
				title: titleText.trim(),
				actress: actress ?? void 0,
				previewSlug: videoInfo.previewSlug ?? void 0
			};
		},
		onMounted: async (id, type, containers, api) => {
			const actressRaw = document.querySelector(".col-md-9 a[href*=\"/star/\"]")?.textContent;
			const actress = MediaUtils.cleanActressName(actressRaw);
			if (actress) cacheSet(`actress_${id}`, actress);
			const videoJs = playerDiv.querySelector(".video-js, #play");
			if (videoJs) videoJs.after(containers.mainElement);
			else playerDiv.after(containers.mainElement);
			if (type === "fc2") {
				const fetchedActress = await fetchActressFromFD2(id);
				if (fetchedActress && containers.linksContainer) api.addActressButton(containers.linksContainer, fetchedActress);
			}
		}
	};
}).withTriggerSelector("#play").withShouldProcessCard((card) => !card.closest("#owl-demo")).withContainerSelector(JAVFC2_CONTAINER_SELECTORS).withCardSelector(JAVFC2_CARD_SELECTORS).withExtraUi((card) => {
	const badges = card.querySelectorAll(JAVFC2_PRESERVED_BADGES);
	if (badges.length === 0) return {};
	return { preservedIconsHTML: Array.from(badges).map((b) => b.cloneNode(true)).map((n) => n.outerHTML).join("") };
}).build();
var MISSAV_LIST_BLACKLIST = [
	"search",
	"new",
	"actress",
	"maker",
	"dm",
	"genres",
	"series",
	"tags",
	"makers"
];
var MISSAV_TITLE_SELECTORS = [
	"a.text-secondary",
	"a.hover\\:text-primary",
	"div.my-2 a",
	"div.mt-1 a",
	".video-title a",
	".thumbnail + div a"
].join(",");
var MISSAV_FALLBACK_LINK_SELECTORS = "a[href*=\"/fc2-ppv-\"], a[href*=\"/en/\"], a[href*=\"/ja/\"], a[href*=\"/cn/\"]";
var MISSAV_HOST_TAG_SELECTORS = [
	"a[x-show*=\"has_chinese_subtitle\"]",
	"a[x-show*=\"has_english_subtitle\"]",
	"a[x-show*=\"is_uncensored_leak\"]"
].join(",");
var getMissavLastSegment = (path) => {
	const segments = path.split("/").filter(Boolean);
	return (segments[segments.length - 1] || "").toLowerCase();
};
var isMissavDetailPattern = (segment) => /^(fc2-ppv-|[a-z]{2,10}-)\d+/i.test(segment);
var isMissavBlacklistedListPath = (segments) => segments.some((segment) => MISSAV_LIST_BLACKLIST.some((blacklist) => segment.toLowerCase().startsWith(blacklist)));
var shouldSkipMissavCard = (card) => {
	return Boolean(card.closest("[x-for*=\"recommend\"]")) || Boolean(card.closest("[x-for*=\"trending\"]")) || Boolean(card.querySelector("[x-text*=\"item.\"]")) || card.hasAttribute("x-show");
};
var resolveMissavTitleLink = (card) => {
	return card.querySelector(MISSAV_TITLE_SELECTORS) || card.querySelector(MISSAV_FALLBACK_LINK_SELECTORS);
};
var resolveMissavPreviewSlug = (video, fallbackSlug) => {
	return video?.dataset.src?.match(/fourhoi\.com\/([^/]+)\/preview\.mp4/)?.[1] || video?.src?.match(/fourhoi\.com\/([^/]+)\/preview\.mp4/)?.[1] || fallbackSlug || null;
};
var resolveMissavHostTags = (card) => {
	return Array.from(card.querySelectorAll(MISSAV_HOST_TAG_SELECTORS)).map((node) => node.textContent?.trim() || "").filter(Boolean);
};
var missav = new AdapterBuilder().withMeta({
	name: "MissAV",
	hostnames: ["missav.ws", "missav.ai"],
	type: "hybrid",
	listOnDetail: true
}).withContextDetector(() => {
	const path = window.location.pathname;
	const segments = path.split("/").filter(Boolean);
	const lastSegment = getMissavLastSegment(path);
	if (isMissavDetailPattern(lastSegment) && segments.length <= 3) return PageContext.Detail;
	if (isMissavBlacklistedListPath(segments)) return PageContext.List;
	if (segments.length === 1 && !MISSAV_LIST_BLACKLIST.includes(lastSegment)) return PageContext.Detail;
	return path.includes("/search/") ? PageContext.List : PageContext.List;
}).withShouldProcessCard((card) => {
	if (shouldSkipMissavCard(card)) return false;
	const anyLink = resolveMissavTitleLink(card);
	const href = anyLink?.getAttribute("href");
	return Boolean(anyLink?.href && href && href !== "#");
}).withCardExtractor((card) => {
	if (shouldSkipMissavCard(card)) return null;
	const img = card.querySelector("img");
	const video = card.querySelector("video");
	const anyLink = resolveMissavTitleLink(card);
	if (!anyLink || !anyLink.getAttribute("href") || anyLink.getAttribute("href") === "#") return null;
	const info = MediaUtils.parseVideoId(anyLink.textContent || "", anyLink.href || "");
	if (!info) return null;
	if (info.id.length < 5 && info.type === "fc2") return null;
	const imageUrl = getBestImageSource(img) || "";
	const previewSlug = resolveMissavPreviewSlug(video, info.previewSlug);
	const tags = resolveMissavHostTags(card);
	const actress = card.querySelector("a[href*=\"/actress/\"]")?.textContent?.trim() || card.querySelector("a[href*=\"/actresses/\"]")?.textContent?.trim();
	const articleUrl = anyLink.href.split("#")[0] || anyLink.href;
	return {
		...info,
		title: anyLink.textContent?.trim() || img?.alt || info.id,
		primaryImageUrl: imageUrl,
		imageUrl,
		fallbackImageUrl: imageUrl,
		articleUrl,
		previewSlug,
		tags,
		actress
	};
}).withDetailEnhancer((el) => {
	const title = document.querySelector("h1.text-base")?.textContent || document.title;
	const info = MediaUtils.parseVideoId(title, location.href);
	if (!info) return null;
	const titleEl = document.querySelector("h1.text-base");
	return {
		mode: "toolbar",
		anchorElement: titleEl?.closest("div.mt-4") || titleEl || el,
		idExtractor: () => info,
		metadataExtractor: (_id, type) => {
			let actress = null;
			if (type === "jav") {
				const contentArea = document.querySelector("div.mt-4, div.text-secondary, main");
				actress = Array.from((contentArea || document).querySelectorAll("a[href*=\"/actresses/\"]")).map((a) => MediaUtils.cleanActressName(a.textContent)).find((a) => !!a) || null;
			}
			return {
				title,
				actress: actress ?? void 0,
				previewSlug: info.previewSlug ?? void 0
			};
		},
		onMounted: async (id, type, containers, api) => {
			if (type === "fc2") {
				const fetchedActress = await fetchActressFromFD2(id);
				if (fetchedActress && containers.linksContainer) api.addActressButton(containers.linksContainer, fetchedActress);
			}
		}
	};
}).withTriggerSelector("div[x-data*=\"player\"], div.player-container, h1.text-base, div.mt-4").withMainImageSelector("div[x-data*=\"player\"] img, .player-container img").withContainerSelector("main, div.grid[class*=\"grid-cols-\"], .sm\\:container > div.grid, div.posts, #main").withCardSelector("div.grid[class*=\"grid-cols-\"] > div.thumbnail.group:not(.fc2-card-rebuilt), div.grid[class*=\"grid-cols-\"] > a.thumbnail.group:not(.fc2-card-rebuilt), div.thumbnail.group:not(.fc2-card-rebuilt), a.thumbnail.group:not(.fc2-card-rebuilt), div.thumbnail:not(.fc2-card-rebuilt), a.thumbnail:not(.fc2-card-rebuilt)").withPostProcessor((card) => {
	card.removeAttribute("x-data");
	card.removeAttribute("x-show");
	card.removeAttribute("x-for");
	card.addEventListener("mouseenter", (e) => e.stopPropagation(), true);
}).build();
var SUPJAV_LIST_CONTAINERS = ".posts.clearfix, .posts";
var SUPJAV_TITLE_TRIGGER = ".archive-title h1, h1.entry-title, .post-title h1";
var SUPJAV_TITLE_BLOCK = ".archive-title, .post-title";
var SUPJAV_MEDIA_BLOCK = "#player-wrap, #player-wrap.player-wrap, .player-wrap, #dz_video, .video-container, .entry-content .video-player";
var SUPJAV_VIEW_BLOCK = ".dz_view";
var SUPJAV_POST_META = ".post-meta.clearfix";
var SUPJAV_EXCLUDE_SELECTORS = ".recommend, .swiper-slide-duplicate";
var findNextSiblingMatching = (element, selector) => {
	let current = element?.nextElementSibling;
	while (current) {
		if (current.matches(selector)) return current;
		if (current.matches(SUPJAV_TITLE_BLOCK) || current.matches(".posts.clearfix, .posts")) break;
		current = current.nextElementSibling;
	}
	return null;
};
var resolveSupjavDetailContext = (titleEl) => {
	const titleAnchor = titleEl.closest(SUPJAV_TITLE_BLOCK) || titleEl;
	const mediaBlock = findNextSiblingMatching(titleAnchor, SUPJAV_MEDIA_BLOCK);
	const viewBlock = findNextSiblingMatching(mediaBlock, SUPJAV_VIEW_BLOCK);
	const postMeta = findNextSiblingMatching(viewBlock || mediaBlock, SUPJAV_POST_META);
	return {
		anchor: postMeta || viewBlock || mediaBlock || titleAnchor,
		postMeta
	};
};
var supjav = new AdapterBuilder().withMeta({
	name: "Supjav",
	hostnames: ["supjav.com"],
	type: "jav",
	listOnDetail: true
}).withContextDetector(() => {
	const path = window.location.pathname;
	if (path.includes("/search/") || window.location.search.includes("s=")) return PageContext.Search;
	const segments = path.replace(/^\/(zh|en|ja)\//, "/").split("/").filter(Boolean);
	if (segments.length === 1 && segments[0] && /^\d+\.html$/.test(segments[0])) return PageContext.Detail;
	if (segments.length === 1 && segments[0]?.toLowerCase() !== "new" && ![
		"popular",
		"maker",
		"cast",
		"tag",
		"category"
	].includes(segments[0]?.toLowerCase() || "")) return PageContext.Detail;
	return PageContext.List;
}).withShouldProcessCard((card) => {
	if (card.closest(SUPJAV_EXCLUDE_SELECTORS)) return false;
	const link = card.querySelector("h3 a, a[rel=\"bookmark\"]");
	if (!link?.href) return false;
	const text = link.title || link.textContent || card.textContent || "";
	return Boolean(MediaUtils.parseVideoId(text, link.href));
}).withIdentityKey((card) => {
	const link = card.querySelector("h3 a, a[rel=\"bookmark\"]");
	if (!link?.href) return null;
	return link.href;
}).withCardExtractor((card) => {
	const link = card.querySelector("h3 a, a[rel=\"bookmark\"]");
	if (!link?.href) return null;
	const text = link.title || link.textContent || card.textContent || "";
	const info = MediaUtils.parseVideoId(text, link.href);
	if (!info) return null;
	const img = card.querySelector("img.thumb, img");
	const imageSource = card.querySelector("img")?.getAttribute("data-original") || getBestImageSource(img) || "";
	return {
		...info,
		title: text || info.id,
		primaryImageUrl: imageSource,
		imageUrl: imageSource,
		fallbackImageUrl: imageSource,
		articleUrl: link.href,
		previewSlug: info.previewSlug,
		actress: card.querySelector(".post-meta a[href*=\"/cast/\"]")?.textContent?.trim() || card.querySelector(".meta a[href*=\"/cast/\"]")?.textContent?.trim()
	};
}).withDetailEnhancer((titleEl) => ({
	mode: "toolbar",
	anchorElement: resolveSupjavDetailContext(titleEl).anchor,
	idExtractor: () => {
		return MediaUtils.parseVideoId(titleEl.textContent || "", location.href) || null;
	},
	metadataExtractor: () => ({
		title: titleEl.textContent?.trim() || "",
		actress: void 0,
		previewSlug: void 0
	}),
	onMounted: async (id, type, containers, api) => {
		if (type === "fc2") {
			const fetchedActress = await fetchActressFromFD2(id);
			if (fetchedActress && containers.linksContainer) api.addActressButton(containers.linksContainer, fetchedActress);
		}
	}
})).withTriggerSelector(SUPJAV_TITLE_TRIGGER).withMainImageSelector("#player-wrap img, .player-wrap img").withContainerSelector(SUPJAV_LIST_CONTAINERS).withCardSelector(".post:has(a.img):not(.fc2-card-rebuilt)").withPostProcessor((card, _el, newCard, data, classes) => {
	if ((data?.title?.includes("[有]") || card.innerText.includes("有码")) && classes?.isCensored) {
		card.classList.add(classes.isCensored);
		newCard?.classList.add(classes.isCensored);
	}
	const meta = card.querySelector(".meta");
	if (meta && newCard && classes) {
		const infoArea = newCard.querySelector(`.${classes.infoArea}`);
		if (infoArea) infoArea.insertBefore(meta.cloneNode(true), infoArea.querySelector(`.${classes.cardActionRow}`));
	}
	if (supjav.context?.supjavSortByViews) {
		const container = card.closest(SUPJAV_LIST_CONTAINERS);
		if (container && container.style.display !== "flex") {
			container.style.display = "flex";
			container.style.flexWrap = "wrap";
		}
		const match = ((meta?.querySelector(".date, .pv, .views, .stats") || meta)?.textContent || card.textContent || "").match(/([\d,.]+)[kKmM]?\s*(?:Views|views|播放)/);
		let views = 0;
		if (match && match[1]) {
			const numStr = match[1].replace(/,/g, "");
			views = parseFloat(numStr);
			const matchText = match[0].toLowerCase();
			if (matchText.includes("k")) views *= 1e3;
			if (matchText.includes("m")) views *= 1e6;
		}
		if (views > 0) card.style.order = `${-Math.round(views)}`;
	}
}).build();
var sitesRegistered = false;
function registerAllSites(registerSiteConfig) {
	if (sitesRegistered) return;
	sitesRegistered = true;
	registerSiteConfig("fc2db", fc2db);
	registerSiteConfig("fd2ppv", fd2ppv);
	registerSiteConfig("supjav", supjav);
	registerSiteConfig("missav", missav);
	registerSiteConfig("javdb", javdb);
	registerSiteConfig("javfc2", javfc2);
	registerSiteConfig("fc2cmadb", fc2cmadb);
}
var GlobalFilterManager = new class FilterManager {
	static REFRESH_DELAY_MS = 80;
	pendingTimer = null;
	lastCounts = null;
	static isSearchPage() {
		const p = location.pathname;
		const s = location.search;
		return p.includes("/search") || s.includes("s=") || s.includes("search") || s.includes("q=") || s.includes("keyword=");
	}
	requestRefresh() {
		if (this.pendingTimer !== null) return;
		this.pendingTimer = setTimeout(() => {
			this.pendingTimer = null;
			this.refreshNow();
		}, FilterManager.REFRESH_DELAY_MS);
	}
	refreshNow() {
		const C = Config.CLASSES;
		const counts = {
			hideViewed: 0,
			hideNoMagnet: 0,
			hideCensored: 0
		};
		document.querySelectorAll(`.${C.cardRebuilt}`).forEach((card) => {
			const el = card;
			if (el.classList.contains(C.hideViewed)) counts.hideViewed += 1;
			if (el.classList.contains(C.hideNoMagnet)) counts.hideNoMagnet += 1;
			if (el.classList.contains(C.hideCensored)) counts.hideCensored += 1;
		});
		if (!this.hasChanged(counts)) return;
		this.syncToBody(counts);
		this.lastCounts = counts;
		CoreEvents.emit(AppEvents.FILTER_COUNTS_CHANGED, counts);
	}
	hasChanged(next) {
		if (!this.lastCounts) return true;
		return this.lastCounts.hideViewed !== next.hideViewed || this.lastCounts.hideNoMagnet !== next.hideNoMagnet || this.lastCounts.hideCensored !== next.hideCensored;
	}
	syncToBody(counts) {
		const d = document.body.dataset;
		const v = String(counts.hideViewed);
		const n = String(counts.hideNoMagnet);
		const c = String(counts.hideCensored);
		if (d.fc2HideViewedCount !== v) d.fc2HideViewedCount = v;
		if (d.fc2HideNoMagnetCount !== n) d.fc2HideNoMagnetCount = n;
		if (d.fc2HideCensoredCount !== c) d.fc2HideCensoredCount = c;
	}
	static resolveCardRoot(el) {
		return el.classList.contains(Config.CLASSES.cardRebuilt) ? el : el.closest(`.${Config.CLASSES.cardRebuilt}`) || el;
	}
	static shouldSkipFilters(target) {
		if (this.isSearchPage()) return true;
		if ((target.classList.contains(Config.CLASSES.processedCard) ? target : target.querySelector(`.${Config.CLASSES.processedCard}`))?.classList.contains("fc2-is-detail")) return true;
		return false;
	}
	applyCardVisibility(c, hasM) {
		if (!c) return;
		const target = FilterManager.resolveCardRoot(c);
		if (FilterManager.shouldSkipFilters(target)) {
			target.classList.remove(Config.CLASSES.hideNoMagnet);
			return;
		}
		const isSearching = (target.classList.contains(Config.CLASSES.processedCard) ? target : target.querySelector(`.${Config.CLASSES.processedCard}`))?.dataset?.enhSearching === "true";
		const shouldHide = resolveService(STATE_SERVICE).proxy.hideNoMagnet && !hasM && !isSearching;
		target.classList.toggle(Config.CLASSES.hideNoMagnet, shouldHide);
	}
	applyCensoredFilter(c) {
		if (!c) return;
		const target = FilterManager.resolveCardRoot(c);
		if (FilterManager.shouldSkipFilters(target)) {
			target.classList.remove(Config.CLASSES.hideCensored);
			return;
		}
		const isCensored = target.classList.contains(Config.CLASSES.isCensored) || !!target.querySelector(`.${Config.CLASSES.isCensored}`);
		target.classList.toggle(Config.CLASSES.hideCensored, resolveService(STATE_SERVICE).proxy.hideCensored && isCensored);
	}
	applyHistoryVisibility(c) {
		if (!c) return;
		const target = FilterManager.resolveCardRoot(c);
		if (FilterManager.shouldSkipFilters(target)) {
			target.classList.remove(Config.CLASSES.hideViewed);
			return;
		}
		const isViewed = resolveService(HISTORY_SERVICE).has(target.dataset.id || "");
		target.classList.toggle(Config.CLASSES.hideViewed, resolveService(STATE_SERVICE).proxy.hideViewed && isViewed);
	}
}();
var log$16 = Logger.scope("ViewStore");
var ViewStoreImpl = class {
	states = /* @__PURE__ */ new Map();
	maxStateEntries;
	constructor(maxStateEntries = 2e3) {
		this.maxStateEntries = maxStateEntries;
		CoreEvents.on(AppEvents.SITE_RESET, () => this.clear());
	}
	getDefaults() {
		return {
			isSearching: false,
			hasMagnet: false,
			hasPreviews: false
		};
	}
	get(id) {
		const normalizedId = IdNormalizer.normalize(id);
		const existing = this.states.get(normalizedId);
		if (existing) {
			this.states.delete(normalizedId);
			this.states.set(normalizedId, existing);
			return existing;
		}
		const initialState = this.getDefaults();
		this.states.set(normalizedId, initialState);
		this.enforceLimit();
		return initialState;
	}
	set(id, key, value) {
		const normalizedId = IdNormalizer.normalize(id);
		const current = this.get(normalizedId);
		if (current[key] === value) return;
		current[key] = value;
		log$16.trace(`[${normalizedId}] ${String(key)} = ${value}`);
		CoreEvents.emit(AppEvents.VIEW_STATE_CHANGED, {
			id: normalizedId,
			key: String(key),
			value
		});
	}
	update(id, updates) {
		Object.entries(updates).forEach(([key, value]) => {
			this.set(id, key, value);
		});
	}
	release(id) {
		const normalizedId = IdNormalizer.normalize(id);
		this.states.delete(normalizedId);
	}
	clear() {
		this.states.clear();
	}
	enforceLimit() {
		if (this.states.size > this.maxStateEntries) {
			const oldestKey = this.states.keys().next().value;
			if (oldestKey !== void 0) this.states.delete(oldestKey);
		}
	}
};
var ViewStore = new ViewStoreImpl();
var log$15 = Logger.scope("MagnetResolver");
var isValidMagnetUrl = (url) => typeof url === "string" && url.startsWith("magnet:?");
/** Resolve magnet for a single card: check cache → fetch from service → emit result */
var resolveMagnetForCard = async (id, type, source) => {
	try {
		const cached = await resolveService(DATA_SERVICE).cache.get(id);
		if (isNegativeMagnetCacheValue(cached)) {
			CoreEvents.emit(AppEvents.MAGNET_FAILED, { id });
			return;
		}
		if (isValidMagnetUrl(cached)) {
			CoreEvents.emit(AppEvents.MAGNET_FOUND, {
				id,
				url: cached
			});
			return;
		}
		handleMagnetResult(id, await resolveService(MAGNET_SERVICE).fetchMagnet(id, type));
	} catch (e) {
		log$15.error(`Magnet lookup failed for ${id} via ${source}`, e);
		handleMagnetResult(id, null);
	}
};
/** Queue a magnet lookup for a card (sets searching state, fires async) */
var queueMagnetLookup = (id, type, source) => {
	ViewStore.set(id, "isSearching", true);
	fireAndForget(resolveMagnetForCard(id, type, source), "MagnetResolver");
};
/** Handle magnet result: update ViewStore and trigger filter refresh */
var handleMagnetResult = (id, url) => {
	const resolvedUrl = isValidMagnetUrl(url) ? url : null;
	ViewStore.set(id, "isSearching", false);
	ViewStore.set(id, "hasMagnet", !!resolvedUrl);
	ViewStore.set(id, "magnetUrl", resolvedUrl);
	GlobalFilterManager.requestRefresh();
};
var isNavigableHref = (href) => {
	return false;
};
/**
* Standardized Button Component
* Supports multiple variants, sizes and modern glassmorphism aesthetics.
*/
var Button = (iconSvg, tip, href, onClick, className = "", options = {}) => {
	const classes = Config.CLASSES;
	const children = [];
	if (iconSvg) children.push(UIUtils.icon(iconSvg));
	if (!options.iconOnly || options.size !== "micro") children.push(h("span", { className: classes.buttonText }, tip));
	children.push(h("span", { className: classes.tooltip }, tip));
	const variantClass = options.variant ? `${options.variant}` : "";
	const sizeClass = options.size ? `${options.size}` : "";
	const iconOnlyClass = options.iconOnly ? "icon-only" : "";
	const combinedClassName = [
		classes.resourceBtn,
		"fc2-enh-btn",
		variantClass,
		sizeClass,
		iconOnlyClass,
		className,
		options.className || ""
	].filter(Boolean).join(" ").trim();
	const buttonTitle = options.title || tip;
	const buttonProps = {
		className: combinedClassName,
		"aria-label": options.ariaLabel || buttonTitle,
		"data-title": options.dataTitle || buttonTitle,
		onclick: (e) => {
			if (!onClick) return;
			if (!isNavigableHref()) e.preventDefault();
			e.stopPropagation();
			onClick(e);
		}
	};
	return isNavigableHref() ? h("a", {
		...buttonProps,
		href,
		target: "_blank",
		rel: "noopener noreferrer"
	}, ...children) : h("button", {
		...buttonProps,
		type: options.type || "button"
	}, ...children);
};
var log$14 = Logger.scope("ActressButton");
var ActressButton = (cont, actress) => {
	if (!cont) return;
	if (!actress?.trim()) {
		log$14.debug("Skip actress button: empty name");
		return;
	}
	if (cont.querySelector(".fc2-btn-actress")) return;
	const name = actress.trim();
	const actBtn = Button(null, name, void 0, () => UIUtils.copyButtonBehavior(actBtn, name, t("tooltipCopied")), "fc2-btn-actress");
	const actionRow = cont.querySelector(`.${Config.CLASSES.cardActionRow}`);
	const links = cont.querySelector(`.${Config.CLASSES.resourceLinksContainer}`);
	if (actionRow) cont.insertBefore(actBtn, actionRow);
	else if (links) cont.insertBefore(actBtn, links);
	else cont.appendChild(actBtn);
	const toolbar = cont.closest(".fc2-enh-toolbar");
	if (toolbar) toolbar.dataset.layout = "with-actress";
};
var getMagnetButtonSelector = () => `.${Config.CLASSES.resourceBtn}.${Config.CLASSES.btnMagnet}`;
var hasMagnetButton = (root) => !!root?.querySelector(getMagnetButtonSelector());
var findMagnetButton = (root) => root?.querySelector(getMagnetButtonSelector()) || null;
var MagnetButton = (cont, url) => {
	if (cont && !hasMagnetButton(cont)) {
		const btn = Button(IconMagnet, t("tooltipCopyMagnet"), void 0, () => UIUtils.copyButtonBehavior(btn, url, t("tooltipCopied")), Config.CLASSES.btnMagnet, {
			ariaLabel: t("tooltipCopyMagnet"),
			title: t("tooltipCopyMagnet")
		});
		cont.appendChild(btn);
		const card = cont.closest(`.${Config.CLASSES.cardRebuilt}`);
		if (card) GlobalFilterManager.applyCardVisibility(card, true);
	}
};
/** Single mount helper for magnet copy button (list/detail/toolbar). */
var mountMagnetButton = MagnetButton;
var hasActionSheetUrl = (url) => typeof url === "string" && url.trim().length > 0;
var ActionSheet = class {
	static backdrop = null;
	static sheet = null;
	static hideTimer = null;
	static touchStartY = 0;
	static touchCurrentY = 0;
	static cancelPendingHide() {
		if (this.hideTimer !== null) {
			window.clearTimeout(this.hideTimer);
			this.hideTimer = null;
		}
	}
	static ensureElements() {
		if (this.backdrop && !this.backdrop.isConnected && this.sheet && !this.sheet.isConnected) {
			this.backdrop = null;
			this.sheet = null;
		}
		if (this.backdrop && this.sheet) return;
		this.backdrop = h("div", {
			className: "fc2-action-sheet-backdrop",
			"aria-hidden": "true"
		});
		this.sheet = h("div", {
			className: "fc2-action-sheet",
			role: "dialog",
			"aria-modal": "true",
			"aria-labelledby": "fc2-sheet-title",
			tabindex: "-1"
		});
		this.sheet.addEventListener("keydown", (e) => {
			if (e.key !== "Tab" || !this.sheet) return;
			const focusable = this.sheet.querySelectorAll("button:not([disabled]), a[href], [tabindex]:not([tabindex=\"-1\"])");
			if (focusable.length === 0) return;
			const first = focusable[0];
			const last = focusable[focusable.length - 1];
			if (e.shiftKey) {
				if (document.activeElement === first || document.activeElement === this.sheet) {
					e.preventDefault();
					last.focus();
				}
			} else if (document.activeElement === last) {
				e.preventDefault();
				first.focus();
			}
		});
		UIHost.add(this.backdrop);
		UIHost.add(this.sheet);
		if (!DeviceService.isMobile()) this.sheet.classList.add("desktop");
		this.backdrop.onclick = (e) => {
			e.preventDefault();
			e.stopPropagation();
			this.hide();
		};
		this.initTouchEvents();
	}
	static createCloseButton() {
		return h("button", {
			className: "fc2-action-sheet-close-btn",
			"aria-label": t("btnClose"),
			onclick: (e) => {
				e.preventDefault();
				this.hide();
			}
		}, "×");
	}
	static createOptionItem(opt) {
		const iconEl = (typeof opt.icon === "string" ? UIUtils.icon(opt.icon) : opt.icon).cloneNode(true);
		if (opt.onSelect) return h("button", {
			className: "fc2-action-sheet-item",
			type: "button",
			onclick: (e) => {
				e.preventDefault();
				e.stopPropagation();
				this.hide();
				window.setTimeout(() => opt.onSelect?.(), 0);
			}
		}, iconEl, h("span", {}, opt.name));
		if (hasActionSheetUrl(opt.url)) {
			const item = h("a", {
				className: "fc2-action-sheet-item",
				href: opt.url,
				target: "_blank",
				rel: "noopener noreferrer"
			}, iconEl, h("span", {}, opt.name));
			item.onclick = () => this.hide();
			return item;
		}
		return h("button", {
			className: "fc2-action-sheet-item",
			type: "button",
			disabled: true,
			"aria-disabled": "true"
		}, iconEl, h("span", {}, opt.name));
	}
	static createOptionsGrid(options) {
		const grid = h("div", { className: "fc2-action-sheet-grid" });
		options.forEach((opt) => {
			grid.appendChild(this.createOptionItem(opt));
		});
		return grid;
	}
	static show(title, options) {
		this.cancelPendingHide();
		this.ensureElements();
		this.sheet.style.transform = "";
		this.sheet.style.transition = "";
		this.touchStartY = 0;
		this.touchCurrentY = 0;
		this.sheet.textContent = "";
		this.sheet.appendChild(h("div", { className: "fc2-action-sheet-handle" }));
		const header = h("div", { className: "fc2-action-sheet-header" }, h("div", {
			className: "fc2-action-sheet-title",
			id: "fc2-sheet-title"
		}, title));
		header.appendChild(this.createCloseButton());
		this.sheet.appendChild(header);
		this.sheet.appendChild(this.createOptionsGrid(options));
		requestAnimationFrame(() => {
			this.backdrop.classList.add("active");
			this.sheet.classList.add("active");
			(this.sheet.querySelector("button:not([disabled]), a[href], [tabindex]:not([tabindex=\"-1\"])") ?? this.sheet).focus();
		});
		OverlayStack.push(this);
	}
	static initTouchEvents() {
		if (!this.sheet) return;
		this.sheet.addEventListener("touchstart", (e) => {
			const touch = e.touches[0];
			if (!touch) return;
			this.touchStartY = touch.clientY;
			if (this.sheet) this.sheet.style.transition = "none";
		}, { passive: true });
		this.sheet.addEventListener("touchmove", (e) => {
			const touch = e.touches[0];
			if (!touch) return;
			this.touchCurrentY = touch.clientY;
			const deltaY = this.touchCurrentY - this.touchStartY;
			if (deltaY > 0 && this.sheet) this.sheet.style.transform = `translateY(${deltaY}px)`;
		}, { passive: true });
		this.sheet.addEventListener("touchend", () => {
			if (!this.sheet) return;
			this.sheet.style.transition = "";
			if (this.touchCurrentY - this.touchStartY > UI_CONSTANTS.SWIPE_DISMISS_THRESHOLD) this.hide();
			else this.sheet.style.transform = "";
			this.touchStartY = 0;
			this.touchCurrentY = 0;
		});
	}
	static close() {
		this.hide();
	}
	static hide() {
		if (!this.backdrop || !this.sheet) return;
		this.cancelPendingHide();
		OverlayStack.remove(this);
		this.backdrop.classList.remove("active");
		this.sheet.classList.remove("active");
		const b = this.backdrop;
		const s = this.sheet;
		this.hideTimer = window.setTimeout(() => {
			if (s && s.parentElement) s.remove();
			if (b && b.parentElement) b.remove();
			if (this.backdrop === b) this.backdrop = null;
			if (this.sheet === s) this.sheet = null;
			if (this.hideTimer !== null) this.hideTimer = null;
		}, TIMING.UI_TRANSITION_NORMAL);
	}
};
var log$13 = Logger.scope("Registry");
var COMP_SYM = Symbol("fc2_comp");
var Registry = class {
	components = /* @__PURE__ */ new Map();
	constructor() {
		this.initGC();
		this.initEvents();
	}
	initEvents() {
		CoreEvents.on(AppEvents.SITE_RESET, () => {
			log$13.debug("Site change detected via events, clearing registry");
			this.clear();
		});
		CoreEvents.on(AppEvents.VIEW_STATE_CHANGED, (payload) => {
			this.notify(payload.id, {
				key: payload.key,
				value: payload.value
			});
		});
	}
	initGC() {
		DomObserver.subscribe((_added, removed) => {
			removed.forEach((node) => this.gc(node));
		});
	}
	gc(root) {
		const elementsWithComponents = root.querySelectorAll("[data-fc2-comp]");
		const checkAndClean = (el) => {
			const set = el[COMP_SYM];
			if (set) Array.from(set).forEach((ins) => {
				log$13.trace(`Auto-GC: detached ${ins.id}`);
				this.unregister(ins);
			});
		};
		if (root.hasAttribute("data-fc2-comp")) checkAndClean(root);
		elementsWithComponents.forEach((el) => checkAndClean(el));
	}
	register(component) {
		if (!this.components.has(component.id)) this.components.set(component.id, /* @__PURE__ */ new Set());
		this.components.get(component.id).add(component);
		if (component.element) {
			const boundElement = component.element;
			let set = boundElement[COMP_SYM];
			if (!set) {
				set = /* @__PURE__ */ new Set();
				boundElement[COMP_SYM] = set;
				component.element.setAttribute("data-fc2-comp", "");
			}
			set.add(component);
		}
		log$13.trace(`Registered: ${component.id}`);
	}
	unregister(component) {
		const id = component.id;
		const set = this.components.get(id);
		if (set) {
			set.delete(component);
			if (set.size === 0) {
				this.components.delete(id);
				ViewStore.release(id);
			}
		}
		if (component.element) {
			const boundElement = component.element;
			const symSet = boundElement[COMP_SYM];
			if (symSet) {
				symSet.delete(component);
				if (symSet.size === 0) {
					delete boundElement[COMP_SYM];
					component.element.removeAttribute("data-fc2-comp");
				}
			}
		}
		if (component.destroy) try {
			component.destroy();
		} catch (e) {
			log$13.warn(`Failed to destroy component ${id}`, e);
		}
	}
	getInstances(id) {
		const set = this.components.get(id);
		return set ? Array.from(set) : [];
	}
	getAllRegisteredIds() {
		return Array.from(this.components.keys());
	}
	notify(id, data) {
		this.getInstances(id).forEach((ins) => {
			try {
				ins.update(data);
			} catch (e) {
				log$13.error(`Notify failed for ${id}`, e);
			}
		});
	}
	clear() {
		const allComponents = [];
		const allIds = /* @__PURE__ */ new Set();
		this.components.forEach((set, id) => {
			allIds.add(id);
			set.forEach((ins) => allComponents.push(ins));
		});
		this.components.clear();
		allComponents.forEach((ins) => {
			if (ins.element) {
				const boundElement = ins.element;
				const symSet = boundElement[COMP_SYM];
				if (symSet) {
					symSet.delete(ins);
					if (symSet.size === 0) {
						delete boundElement[COMP_SYM];
						ins.element.removeAttribute("data-fc2-comp");
					}
				}
			}
			if (ins.destroy) try {
				ins.destroy();
			} catch (e) {
				log$13.warn(`Failed to destroy component ${ins.id}`, e);
			}
		});
		allIds.forEach((id) => ViewStore.release(id));
		log$13.debug("Component registry cleared");
	}
};
var ComponentRegistry = new Registry();
var log$12 = Logger.scope("HistoryManager");
var HistoryManager = class {
	isSyncing = false;
	pendingSync = false;
	unsubscribers = [];
	constructor() {
		this.unsubscribers.push(CoreEvents.on(AppEvents.HISTORY_LOADED, () => this.requestSync()));
		this.unsubscribers.push(CoreEvents.on(AppEvents.UI_READY, () => this.requestSync()));
		this.unsubscribers.push(CoreEvents.on(AppEvents.HISTORY_CHANGED, () => this.requestSync()));
	}
	/**
	* Request a sync, coalescing multiple rapid requests into one.
	*/
	requestSync() {
		if (this.isSyncing) {
			this.pendingSync = true;
			return;
		}
		this.syncAllComponents();
	}
	schedulePendingSync() {
		if (this.pendingSync) {
			this.pendingSync = false;
			requestAnimationFrame(() => this.syncAllComponents());
		}
	}
	normalizeId(id) {
		return IdNormalizer.normalize(id);
	}
	/**
	* Toggles the 'watched' status for a specific media ID.
	* Updates HistoryService, ViewStore, and triggers a filter refresh.
	*/
	async toggleWatched(id, isManual = true) {
		try {
			const normalizedId = IdNormalizer.normalize(id);
			const isCurrentlyWatched = resolveService(HISTORY_SERVICE).has(normalizedId, "watched");
			const targetStatus = isCurrentlyWatched ? "none" : "watched";
			if (isCurrentlyWatched) await resolveService(HISTORY_SERVICE).remove(normalizedId);
			else await resolveService(HISTORY_SERVICE).add(normalizedId, "watched");
			ViewStore.set(normalizedId, "status", targetStatus);
			CoreEvents.emit(AppEvents.HISTORY_CHANGED, {});
			GlobalFilterManager.requestRefresh();
			log$12.debug(`Toggled watched status for ${normalizedId}: ${targetStatus} (Manual: ${isManual})`);
		} catch (error) {
			log$12.error(`Failed to toggle watched status for ${id}`, error);
		}
	}
	/**
	* Marks the 'watched' status for a specific media ID without toggling.
	*/
	async setWatched(id) {
		try {
			const normalizedId = IdNormalizer.normalize(id);
			if (resolveService(HISTORY_SERVICE).has(normalizedId, "watched")) return;
			await resolveService(HISTORY_SERVICE).add(normalizedId, "watched");
			ViewStore.set(normalizedId, "status", "watched");
			CoreEvents.emit(AppEvents.HISTORY_CHANGED, {});
			GlobalFilterManager.requestRefresh();
			log$12.debug(`Set watched status for ${normalizedId}`);
		} catch (error) {
			log$12.error(`Failed to set watched status for ${id}`, error);
		}
	}
	/**
	* Batch updates for multiple IDs.
	* Useful for 'Mark all as read' or similar bulk actions.
	*/
	async markBatchWatched(ids) {
		log$12.info(`Batch marking ${ids.length} items as watched`);
		await Promise.all(ids.map(async (id) => {
			const normalizedId = IdNormalizer.normalize(id);
			await resolveService(HISTORY_SERVICE).add(normalizedId, "watched");
			ViewStore.set(normalizedId, "status", "watched");
		}));
		CoreEvents.emit(AppEvents.HISTORY_CHANGED, {});
		GlobalFilterManager.requestRefresh();
	}
	/**
	* Synchronizes all registered components with the current history state.
	*/
	syncAllComponents() {
		this.isSyncing = true;
		const ids = ComponentRegistry.getAllRegisteredIds();
		log$12.debug(`Syncing ${ids.length} components with history`);
		ids.forEach((id) => {
			const status = resolveService(HISTORY_SERVICE).getStatus(id);
			if (status) ViewStore.set(id, "status", status);
		});
		this.isSyncing = false;
		this.schedulePendingSync();
	}
	/**
	* Synchronizes a single card's DOM state with the history status.
	* This is a low-level helper for non-component cards or manual DOM patches.
	*/
	syncElement(el, id, classes) {
		const isWatched = resolveService(HISTORY_SERVICE).has(id, "watched");
		el.classList.toggle(classes.isViewed, isWatched);
	}
	destroy() {
		this.unsubscribers.forEach((fn) => fn());
		this.unsubscribers.length = 0;
	}
};
var GlobalHistoryManager = new HistoryManager();
var getInitialToggleState = (id, type) => {
	return resolveService(HISTORY_SERVICE).getStatus(id) === type;
};
var getToggleVisualConfig = () => ({
	activeClass: "fc2-is-viewed",
	className: "fc2-btn-toggle-view",
	iconClass: "fc2-icon-viewed",
	iconOff: IconEyeSlash,
	iconOn: IconEye,
	tooltipOff: t("tooltipMarkAsViewed"),
	tooltipOn: t("tooltipMarkAsUnviewed")
});
var decorateToggleIcons = (button, config, type) => {
	const firstIcon = button.querySelector(".fc2-icon");
	if (firstIcon) {
		firstIcon.classList.add(config.iconClass);
		const iconOffClass = `fc2-icon-un${"viewed" }`;
		firstIcon.after(UIUtils.icon(config.iconOff, iconOffClass));
	}
};
var runToggleAnimation = (button) => {
	button.classList.add("fc2-animate-pop");
	window.setTimeout(() => button.classList.remove("fc2-animate-pop"), TIMING.UI_TRANSITION_NORMAL);
};
var syncToggleAccessibility = (button, label) => {
	button.setAttribute("aria-label", label);
	button.setAttribute("data-title", label);
};
var applyToggleState = (button, isActive, config, _type) => {
	const classes = Config.CLASSES;
	const currentTip = isActive ? config.tooltipOn : config.tooltipOff;
	button.classList.toggle(config.activeClass, isActive);
	if (isActive) runToggleAnimation(button);
	const tooltip = button.querySelector(`.${classes.tooltip}`);
	if (tooltip) tooltip.textContent = currentTip;
	const buttonText = button.querySelector(`.${classes.buttonText}`);
	if (buttonText) buttonText.textContent = currentTip;
	button.setAttribute("aria-pressed", String(isActive));
	syncToggleAccessibility(button, currentTip);
};
var handleToggleClick = async (_button, id, _type, _activeClass) => {
	await GlobalHistoryManager.toggleWatched(id, true);
};
var isTargetUpdate = (_type, key) => key === "status";
var StatusToggle = (id, type) => {
	const config = getToggleVisualConfig();
	const isActive = getInitialToggleState(id, type);
	const currentLabel = isActive ? config.tooltipOn : config.tooltipOff;
	const button = Button(config.iconOn, currentLabel, void 0, () => {
		handleToggleClick(button, id);
	}, `${config.className} ${isActive ? config.activeClass : ""}`, {
		ariaLabel: currentLabel,
		title: currentLabel
	});
	button.setAttribute("aria-pressed", String(isActive));
	decorateToggleIcons(button, config);
	const component = {
		id,
		element: button,
		update: (raw) => {
			const data = raw;
			if (!isTargetUpdate(type, data?.key)) return;
			const isNextActive = data?.value === type;
			applyToggleState(button, isNextActive, config);
		}
	};
	ComponentRegistry.register(component);
	return component;
};
var UIToolbar = { createDetailToolbar: (data, addPreviewButton, addPlayButton) => {
	const { id, type, actress } = data;
	const C = Config.CLASSES;
	const toolbar = h("div", {
		className: "fc2-enh-toolbar fc2-enh-toolbar--detail",
		dataset: { layout: actress ? "with-actress" : "without-actress" }
	});
	const infoArea = h("div", { className: C.infoArea });
	const ctrls = h("div", { className: "fc2-card-top-right-controls fc2-toolbar-section-controls" });
	if (resolveService(STATE_SERVICE).proxy.enableHistory) {
		const toggle = StatusToggle(id, "watched");
		if (toggle && toggle.element) ctrls.appendChild(toggle.element);
	}
	const badge = Button("", id, void 0, (e) => {
		e.preventDefault();
		e.stopPropagation();
		UIUtils.copyButtonBehavior(badge, id, t("tooltipCopied"));
	}, C.fc2IdBadge);
	const btnText = badge.querySelector(`.${C.buttonText}`);
	if (btnText) btnText.textContent = id;
	ctrls.appendChild(badge);
	const links = h("div", { className: `${C.resourceLinksContainer} fc2-toolbar-section-links`.trim() });
	addPlayButton(links, id, type, data.previewSlug);
	if (type === "fc2" && resolveService(STATE_SERVICE).proxy.loadExtraPreviews) addPreviewButton(links, id);
	if (resolveService(STATE_SERVICE).proxy.enableExternalLinks) {
		const trigger = Button(IconLink, t("labelExternalLinks"), void 0, (e) => {
			e.preventDefault();
			e.stopPropagation();
			const portals = AppContainer.resolve(PORTAL_SERVICE).getAvailablePortals({
				id,
				type
			});
			if (portals.length > 0) {
				const options = portals.map((p) => ({
					...p,
					icon: PORTAL_ICONS[p.id] || "<svg viewBox=\"0 0 640 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5l112.2-112.3c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0z\"/></svg>"
				}));
				ActionSheet.show(`${id} - ${t("labelExternalLinks")}`, options);
			} else Toast.show(t("alertNoExternalLinks"), "info");
		});
		links.appendChild(trigger);
	}
	infoArea.appendChild(ctrls);
	if (actress) ActressButton(infoArea, actress);
	infoArea.appendChild(links);
	toolbar.appendChild(infoArea);
	resolveService(MAGNET_SERVICE).fetchMagnet(id, type).then((url) => {
		if (url && url.startsWith("magnet:?")) mountMagnetButton(links, url);
	});
	return toolbar;
} };
var PreviewLogic = class {
	static activePreviewTokens = /* @__PURE__ */ new WeakMap();
	static previewAbortControllers = /* @__PURE__ */ new WeakMap();
	static warmCleanupBindings = /* @__PURE__ */ new WeakMap();
	static activeVideos = /* @__PURE__ */ new Set();
	static lifecycleObservers = /* @__PURE__ */ new WeakMap();
	static lifecycleCards = /* @__PURE__ */ new Set();
	static containerBindings = /* @__PURE__ */ new Map();
	static registerContainer(container) {
		const existing = this.containerBindings.get(container);
		if (existing) {
			container.removeEventListener(existing.mode, existing.handler, existing.capture);
			this.containerBindings.delete(container);
		}
		const mode = resolveService(STATE_SERVICE).proxy.previewMode;
		if (mode === "static") return;
		const selector = `.${Config.CLASSES.processedCard}`;
		const isTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0;
		const handler = (event) => {
			const target = event.target;
			if (isTouch) {
				const previewArea = target.closest(`.${Config.CLASSES.videoPreviewContainer}`);
				if (!previewArea) return;
				const card = previewArea.closest(selector);
				if (!card) return;
				event.preventDefault();
				event.stopPropagation();
				if (card.querySelector(`video.${Config.CLASSES.previewElement}`)) {
					this.disposePreviewForCard(card);
					return;
				}
				if (!previewArea.querySelector(`img.${Config.CLASSES.staticPreview}`)) return;
				this.loadVideoProgressive(card);
			} else if (mode === "hover") {
				const card = target.closest(selector);
				if (card && target === card) this.loadVideoProgressive(card);
			}
		};
		const eventMode = isTouch ? "click" : "mouseenter";
		container.addEventListener(eventMode, handler, true);
		this.containerBindings.set(container, {
			mode: eventMode,
			handler,
			capture: true
		});
	}
	static disposePreviewForCard(card) {
		this.detachLifecycle(card);
		this.previewAbortControllers.get(card)?.abort();
		this.previewAbortControllers.delete(card);
		this.activePreviewTokens.delete(card);
		const video = card.querySelector("video");
		if (video) {
			this.activeVideos.delete(video);
			this.disposePreviewElement(video);
		}
		const binding = this.warmCleanupBindings.get(card);
		if (binding) {
			card.removeEventListener("mouseleave", binding.listener, binding.capture);
			this.warmCleanupBindings.delete(card);
		}
	}
	static async loadVideoProgressive(card) {
		const container = card.querySelector(`.${Config.CLASSES.videoPreviewContainer}`);
		const image = container?.querySelector(`img.${Config.CLASSES.staticPreview}`);
		if (!container || !image) return;
		const { id, type, previewSlug } = card.dataset;
		if (!id || !type) return;
		if (this.tryUseCachedPreview(card, container, image, id)) return;
		if (this.tryUseExistingVideo(card, container, image)) return;
		this.cleanupStaleVideo(container);
		this.previewAbortControllers.get(card)?.abort();
		this.hideErrorIndicator(container);
		this.showLoadingIndicator(container);
		const video = this.createPreviewVideoElement();
		this.activeVideos.add(video);
		container.appendChild(video);
		const previewToken = Symbol(id);
		this.activePreviewTokens.set(card, previewToken);
		const abortController = new AbortController();
		this.previewAbortControllers.set(card, abortController);
		let isStillHovered = true;
		this.attachWarmCleanup(card, video, image, container, () => {
			isStillHovered = false;
			abortController.abort();
		});
		this.attachProgressListener(video, container, () => isStillHovered);
		const reveal = () => this.revealPreview(container, video, image);
		video.addEventListener("loadeddata", reveal, { once: true });
		video.addEventListener("playing", () => this.handlePlayingEvent(video, id, () => isStillHovered, reveal), { once: true });
		video.addEventListener("error", () => {
			if (isStillHovered) this.restoreStaticPreview(container, image, video);
		});
		await this.fetchAndPlayPreview(card, container, image, video, id, type, previewSlug, previewToken, () => isStillHovered, abortController.signal);
	}
	static tryUseCachedPreview(card, container, image, id) {
		const cached = resolveService(PREVIEW_SERVICE).getCachedElement(id);
		if (!cached || !(cached instanceof HTMLVideoElement)) return false;
		if (cached.parentNode !== container) {
			cached.remove();
			container.appendChild(cached);
		}
		this.showActivePreview(container, image, cached);
		this.attachWarmCleanup(card, cached, image, container);
		return true;
	}
	static tryUseExistingVideo(card, container, image) {
		const existingVideo = container.querySelector("video");
		if (!existingVideo || !(existingVideo.currentSrc || existingVideo.getAttribute("src"))) return false;
		this.showActivePreview(container, image, existingVideo);
		this.attachWarmCleanup(card, existingVideo, image, container);
		return true;
	}
	static cleanupStaleVideo(container) {
		const existingVideo = container.querySelector("video");
		if (existingVideo) {
			this.activeVideos.delete(existingVideo);
			this.disposePreviewElement(existingVideo);
		}
	}
	static createPreviewVideoElement() {
		return h("video", {
			referrerpolicy: "no-referrer",
			autoplay: true,
			loop: true,
			muted: true,
			playsInline: true,
			preload: "auto",
			className: `${Config.CLASSES.previewElement} ${Config.CLASSES.hidden}`
		});
	}
	static attachProgressListener(video, container, isStillHovered) {
		video.addEventListener("progress", () => {
			if (video.buffered.length > 0 && isStillHovered() && video.isConnected) {
				const percent = video.buffered.end(0) / video.duration * 100;
				const progress = container.querySelector(".preview-progress");
				if (progress) progress.textContent = `${Math.round(percent)}%`;
			}
		});
	}
	static revealPreview(container, video, image) {
		container.classList.add("fc2-preview-active");
		video.classList.remove(Config.CLASSES.hidden);
		video.classList.add("fc2-reveal-content");
		image.classList.add(Config.CLASSES.hidden);
		this.hideLoadingIndicator(container);
		this.hideErrorIndicator(container);
	}
	static handlePlayingEvent(video, id, isStillHovered, reveal) {
		if (!isStillHovered() || !video.isConnected) {
			video.pause();
			resolveService(PREVIEW_SERVICE).cacheElement(id, video);
			return;
		}
		requestAnimationFrame(() => {
			if (isStillHovered() && video.isConnected) reveal();
			else {
				video.pause();
				resolveService(PREVIEW_SERVICE).cacheElement(id, video);
			}
		});
	}
	static async fetchAndPlayPreview(card, container, image, video, id, type, previewSlug, previewToken, isStillHovered, signal) {
		try {
			const blob = await resolveService(PREVIEW_SERVICE).getPreviewBlob(id, type, previewSlug, signal);
			if (!isStillHovered() || !card.isConnected || this.activePreviewTokens.get(card) !== previewToken) {
				this.activeVideos.delete(video);
				this.disposePreviewElement(video);
				return;
			}
			video.src = URL.createObjectURL(blob);
			video.load();
			video.play().catch(() => {});
		} catch (error) {
			this.handlePreviewError(card, container, image, video, previewToken, isStillHovered, error);
		}
	}
	static handlePreviewError(card, container, image, video, previewToken, isStillHovered, error) {
		if (error instanceof DOMException && error.name === "AbortError") {
			this.disposePreviewElement(video);
			this.activeVideos.delete(video);
			return;
		}
		const rateLimitError = error;
		if (typeof rateLimitError?.retryAfterMs === "number") {
			if (isStillHovered() && card.isConnected && this.activePreviewTokens.get(card) === previewToken) setTimeout(() => {
				if (isStillHovered() && card.isConnected && this.activePreviewTokens.get(card) === previewToken) this.loadVideoProgressive(card);
			}, rateLimitError.retryAfterMs);
			return;
		}
		if (isStillHovered() && card.isConnected && this.activePreviewTokens.get(card) === previewToken) this.restoreStaticPreview(container, image, video, true);
		else {
			this.activeVideos.delete(video);
			this.disposePreviewElement(video);
		}
	}
	static showActivePreview(container, image, video) {
		container.classList.add("fc2-preview-active");
		video.classList.remove(Config.CLASSES.hidden);
		video.classList.add("fc2-reveal-content");
		image.classList.add(Config.CLASSES.hidden);
		video.play().catch(() => {});
	}
	static restoreStaticPreview(container, image, video, showError = true) {
		container.classList.remove("fc2-preview-active");
		image.classList.remove(Config.CLASSES.hidden);
		this.hideLoadingIndicator(container);
		this.disposePreviewElement(video);
		if (showError) this.showErrorIndicator(container);
	}
	static attachWarmCleanup(card, video, image, container, onCleanup) {
		const cleanup = () => {
			onCleanup?.();
			container.classList.remove("fc2-preview-active");
			if (video.isConnected) {
				video.pause();
				video.classList.add(Config.CLASSES.hidden);
				video.classList.remove("fc2-reveal-content");
			}
			image.classList.remove(Config.CLASSES.hidden);
			this.hideLoadingIndicator(container);
			const id = card.dataset.id;
			if (id && (video.currentSrc || video.getAttribute("src"))) resolveService(PREVIEW_SERVICE).cacheElement(id, video);
			this.hideErrorIndicator(container);
		};
		const existing = this.warmCleanupBindings.get(card);
		if (existing) card.removeEventListener("mouseleave", existing.listener, existing.capture);
		card.addEventListener("mouseleave", cleanup, { once: true });
		this.warmCleanupBindings.set(card, {
			listener: cleanup,
			capture: false
		});
		if (typeof IntersectionObserver !== "undefined") {
			this.detachLifecycle(card);
			const observer = new IntersectionObserver((entries) => {
				const entry = entries[0];
				if (entry && !entry.isIntersecting) {
					this.detachLifecycle(card);
					cleanup();
				}
			}, { threshold: 0 });
			observer.observe(card);
			this.lifecycleObservers.set(card, observer);
			this.lifecycleCards.add(card);
		}
	}
	static detachLifecycle(card) {
		this.lifecycleObservers.get(card)?.disconnect();
		this.lifecycleObservers.delete(card);
		this.lifecycleCards.delete(card);
	}
	static showLoadingIndicator(container) {
		if (container.querySelector(".preview-loading")) return;
		const loader = h("div", { className: "preview-loading" }, h("div", { className: "preview-spinner" }), h("div", { className: "preview-progress" }, t("labelLoading")));
		container.appendChild(loader);
	}
	static hideLoadingIndicator(container) {
		container.querySelector(".preview-loading")?.remove();
	}
	static showErrorIndicator(container) {
		this.hideErrorIndicator(container);
		const error = h("div", { className: "preview-error" }, h("span", {
			className: "fc2-icon preview-error-icon",
			innerHTML: IconTriangleExclamation
		}), h("div", {}, t("alertPreviewLoadFailed")));
		container.appendChild(error);
		setTimeout(() => error.remove(), TIMING.PREVIEW_ERROR_DELAY);
	}
	static hideErrorIndicator(container) {
		container.querySelectorAll(".preview-error").forEach((e) => e.remove());
	}
	static disposePreviewElement(element) {
		if (element instanceof HTMLVideoElement) {
			const src = element.src || element.currentSrc || element.getAttribute("src");
			element.pause();
			element.removeAttribute("src");
			try {
				element.load();
			} catch {}
			element.remove();
			if (src?.startsWith("blob:")) URL.revokeObjectURL(src);
		} else element.remove();
	}
	static clearActive() {
		this.lifecycleCards.forEach((card) => this.disposePreviewForCard(card));
		this.lifecycleCards.clear();
		this.activeVideos.forEach((v) => this.disposePreviewElement(v));
		this.activeVideos.clear();
	}
};
var resolveActionLabel = (button) => button.getAttribute("data-title") || button.getAttribute("aria-label") || button.getAttribute("title") || button.textContent?.trim() || t("btnMoreOptions");
var resolveActionIcon = (button) => {
	const icon = button.querySelector(".fc2-icon");
	return icon ? icon.cloneNode(true) : IconGear;
};
var buildMoreActionOptions = (links) => Array.from(links.children).filter((child) => child instanceof HTMLElement).filter((el) => window.getComputedStyle(el).display !== "none").map((button, index) => ({
	id: `${resolveActionLabel(button).replace(/\s+/g, "-").toLowerCase()}-${index}`,
	name: resolveActionLabel(button),
	icon: resolveActionIcon(button),
	onSelect: () => button.click()
}));
var createPortalButton = (data, minimal) => {
	if (minimal || !resolveService(STATE_SERVICE).proxy.enableExternalLinks) return null;
	return Button(IconLink, t("labelExternalLinks"), void 0, (event) => {
		event.preventDefault();
		event.stopPropagation();
		const portals = resolveService(PORTAL_SERVICE).getAvailablePortals(data);
		if (portals.length > 0) {
			const options = portals.map((p) => ({
				...p,
				icon: PORTAL_ICONS[p.id] || "<svg viewBox=\"0 0 640 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5l112.2-112.3c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0z\"/></svg>"
			}));
			ActionSheet.show(t("labelExternalLinks"), options);
		} else Toast.show(t("alertNoExternalLinks"), "info");
	}, "btn-external-links", {
		ariaLabel: t("tooltipOpenExternalLinks"),
		title: t("tooltipOpenExternalLinks")
	});
};
var isJavdbCard = (data) => {
	return data.site === "javdb" || data.articleUrl?.includes("://javdb.com/") || window.location.hostname.includes("javdb");
};
var applyAdaptiveJavdbPreviewRatio = (img, data) => {
	if (!isJavdbCard(data)) return;
	const container = img.parentElement;
	if (!container) return;
	const { naturalWidth, naturalHeight } = img;
	if (!naturalWidth || !naturalHeight) return;
	if (naturalWidth / naturalHeight > 1.05) {
		container.style.setProperty("--fc2-javdb-cover-ratio", `${naturalWidth} / ${naturalHeight}`);
		return;
	}
	container.style.removeProperty("--fc2-javdb-cover-ratio");
};
var createThumbnail = (data, classes) => {
	const img = h("img", {
		alt: data.title || data.id,
		className: `${classes.staticPreview} ${classes.previewElement}`,
		onload: function() {
			this.parentElement?.classList.replace("fc2-skeleton", "is-loaded");
			applyAdaptiveJavdbPreviewRatio(this, data);
			this.classList.add("fc2-reveal-content");
		}
	});
	smartLoadMedia(img, data.primaryImageUrl || data.imageUrl || UI_CONSTANTS.PLACEHOLDER_IMAGE, data.imageUrl, data.fallbackImageUrl, ...data.extraFallbacks || []);
	return h(data.articleUrl ? "a" : "div", {
		className: `${classes.videoPreviewContainer} fc2-skeleton`,
		...data.articleUrl ? {
			href: data.articleUrl,
			target: "_blank",
			rel: "noopener noreferrer"
		} : {}
	}, img);
};
var createHeaderRow = (idBadge, healthIndicator, statusToggle) => h("div", { className: "fc2-card-meta-row" }, h("div", { className: "fc2-card-identity-slot" }, idBadge), h("div", {
	className: "fc2-card-status-slot",
	onclick: (e) => e.stopPropagation()
}, healthIndicator, statusToggle?.element ? statusToggle.element : null));
var createPlayButton = (data) => {
	return Button(IconPlay, t("labelPlayPreview"), void 0, (event) => {
		event.preventDefault();
		event.stopPropagation();
		const card = event.currentTarget.closest(`.${Config.CLASSES.processedCard}`);
		if (card) {
			const playEvent = new CustomEvent("fc2-play-fullscreen", {
				detail: {
					card,
					data
				},
				bubbles: true,
				composed: true
			});
			card.dispatchEvent(playEvent);
		}
	}, Config.CLASSES.btnPlayFullscreen, {
		iconOnly: true,
		size: "micro"
	});
};
var createGalleryButton = (data) => {
	return Button(IconImages, t("extraPreviewTitle"), void 0, (event) => {
		event.preventDefault();
		event.stopPropagation();
		const card = event.currentTarget.closest(`.${Config.CLASSES.processedCard}`);
		if (card) {
			const galleryEvent = new CustomEvent("fc2-open-gallery", {
				detail: {
					card,
					data
				},
				bubbles: true,
				composed: true
			});
			card.dispatchEvent(galleryEvent);
		}
	}, "btn-open-gallery", {
		iconOnly: true,
		size: "micro"
	});
};
var buildActionArea = (data, minimal, links) => {
	const portalEntry = createPortalButton({
		id: data.id,
		type: data.type
	}, minimal);
	const playEntry = createPlayButton(data);
	const galleryEntry = data.type === "fc2" && resolveService(STATE_SERVICE).proxy.loadExtraPreviews ? createGalleryButton(data) : null;
	return {
		portalEntry,
		playEntry,
		galleryEntry,
		secondaryActions: h("div", { className: "fc2-resource-links-container fc2-card-secondary-actions" }, playEntry, galleryEntry, portalEntry, Button(IconGear, t("btnMoreOptions"), void 0, (event) => {
			event.preventDefault();
			event.stopPropagation();
			const options = buildMoreActionOptions(links);
			if (options.length > 0) ActionSheet.show(t("btnMoreOptions"), options);
		}, "btn-more-actions", {
			ariaLabel: t("btnMoreOptions"),
			title: t("btnMoreOptions")
		}))
	};
};
var createCardTitle = (data, classes) => {
	if (!data.title) return null;
	return data.articleUrl ? h("a", {
		className: classes.customTitle,
		href: data.articleUrl,
		target: "_blank",
		rel: "noopener noreferrer",
		title: data.title,
		onclick: (event) => {
			event.stopPropagation();
			GlobalHistoryManager.toggleWatched(data.id, true);
		}
	}, data.title) : h("span", {
		className: classes.customTitle,
		title: data.title
	}, data.title);
};
var buildCardMetaParts = (actress, site, tags, hasAvatar = false) => {
	const parts = [];
	if (actress && !hasAvatar) parts.push(actress);
	if (site) parts.push(site.toUpperCase());
	if (tags) parts.push(...tags.slice(0, 5));
	return parts.filter(Boolean);
};
var resolveHealthIndicatorMeta = (lastCheck, now = Date.now()) => {
	const isRecent = now - lastCheck < 864e5;
	return {
		isRecent,
		title: isRecent ? t("tooltipHealthRecent") : `${t("tooltipHealthStale")}: ${new Date(lastCheck).toLocaleDateString()}`,
		symbol: isRecent ? "?" : "?"
	};
};
var createActressUnit = (info) => h("div", {
	className: `fc2-actress-unit${info.url ? " fc2-actress-unit-linkable" : ""}`,
	title: info.name,
	...info.url ? {
		tabindex: "0",
		role: "link"
	} : {},
	onclick: (e) => {
		if (info.url) {
			e.stopPropagation();
			BrowserService.openTab(info.url);
		}
	},
	onkeydown: info.url ? (e) => {
		if (e.key === "Enter" || e.key === " ") {
			e.preventDefault();
			e.stopPropagation();
			BrowserService.openTab(info.url);
		}
	} : void 0
}, info.avatarUrl ? h("img", {
	src: info.avatarUrl,
	alt: info.name || "",
	className: "fc2-actress-avatar-original",
	onerror: function() {
		this.classList.add("fc2-actress-avatar-hidden");
	}
}) : null, info.name ? h("span", { className: "fc2-actress-name-original" }, info.name) : null);
var createActressProfile = (data) => {
	const list = [...data.actresses || []];
	if (list.length === 0 && (data.actress || data.actressAvatarUrl)) list.push({
		name: data.actress || "",
		avatarUrl: data.actressAvatarUrl,
		url: data.actressUrl
	});
	if (list.length === 0) return null;
	return h("div", { className: "fc2-actress-profile-badge-original" }, ...list.map(createActressUnit));
};
var createHealthIndicator = (lastCheck) => {
	if (!lastCheck) return null;
	const meta = resolveHealthIndicatorMeta(lastCheck);
	return h("div", {
		className: `fc2-health-indicator ${meta.isRecent ? "fc2-health-indicator--recent" : "fc2-health-indicator--stale"}`,
		title: meta.title,
		"aria-label": meta.title
	}, meta.symbol);
};
var isPrimaryAction = (action) => {
	const actions = resolveService(STATE_SERVICE).proxy.cardPrimaryActions;
	return Array.isArray(actions) ? actions.includes(action) : [
		"id",
		"viewed",
		"play",
		"preview",
		"magnet",
		"external"
	].includes(action);
};
var createBadge = (id, classes) => {
	const badge = Button("", id, void 0, (event) => {
		event.preventDefault();
		event.stopPropagation();
		UIUtils.copyButtonBehavior(badge, id, t("tooltipCopied"));
		badge.classList.add("fc2-pulse-once");
		setTimeout(() => badge.classList.remove("fc2-pulse-once"), TIMING.UI_ANIMATION_BADGE);
	}, classes.fc2IdBadge, {
		ariaLabel: t("tooltipCopyId"),
		title: t("tooltipCopyId"),
		dataTitle: id
	});
	const text = badge.querySelector(`.${classes.buttonText}`);
	if (text) text.textContent = id;
	return badge;
};
var createEnhancedCardMeta = (classes, items) => items.length === 0 ? null : h("div", {
	className: classes.cardMeta,
	title: items.join(" · ")
}, items.join(" · "));
var createEnhancedCardView = ({ classes, data, onCardClick, minimal, suppressNativeDetailMeta = false }) => {
	const statusToggle = resolveService(STATE_SERVICE).proxy.enableHistory ? StatusToggle(data.id, "watched") : null;
	const idBadge = createBadge(data.id, classes);
	const healthIndicator = createHealthIndicator(data.lastCheck);
	const primaryLinks = h("div", { className: "fc2-resource-links-container fc2-card-primary-actions" });
	const links = h("div", {
		className: "fc2-resource-links-container fc2-card-overflow-actions fc2-card-action-stash",
		hidden: true
	});
	if (!isPrimaryAction("id")) links.appendChild(idBadge);
	if (!isPrimaryAction("viewed") && statusToggle?.element) links.appendChild(statusToggle.element);
	const { portalEntry, galleryEntry, playEntry, secondaryActions } = buildActionArea(data, minimal, links);
	const meta = (data.site || "").toLowerCase() !== "fd2ppv" ? createEnhancedCardMeta(classes, buildCardMetaParts(data.actress, data.site, data.tags, !!data.actress || !!data.actressAvatarUrl || !!(data.actresses && data.actresses.length > 0))) : null;
	const card = h("div", {
		className: `${classes.processedCard} ${classes.cardRebuilt} ${data.type}-card ${data.customClass || ""} ${minimal ? "fc2-is-minimal" : ""}`,
		dataset: {
			id: data.id,
			type: data.type,
			previewSlug: data.previewSlug || "",
			enhSearching: "true",
			hasMagnet: "false"
		},
		onclick: onCardClick
	});
	const selectionOverlay = h("div", { className: "fc2-card-selection-overlay" }, h("input", {
		type: "checkbox",
		className: "fc2-card-selection-checkbox",
		onclick: (e) => e.stopPropagation()
	}));
	const safePreservedIconsHTML = data.preservedIconsHTML ? (() => {
		const temp = document.createElement("div");
		temp.innerHTML = data.preservedIconsHTML;
		temp.querySelectorAll("script, style, iframe, object, embed, link, meta, form, base, [onload], [onerror], [onclick], [onmouseover], [onfocus]").forEach((el) => el.remove());
		return temp.innerHTML;
	})() : null;
	const infoArea = h("div", { className: classes.infoArea }, createHeaderRow(idBadge, healthIndicator, statusToggle), suppressNativeDetailMeta ? null : createActressProfile(data), suppressNativeDetailMeta ? null : createCardTitle(data, classes), safePreservedIconsHTML ? h("div", {
		className: classes.preservedIconsContainer,
		innerHTML: safePreservedIconsHTML
	}) : null, meta, h("div", { className: classes.cardActionRow }, secondaryActions), links);
	card.appendChild(createThumbnail(data, classes));
	card.appendChild(infoArea);
	card.appendChild(selectionOverlay);
	return {
		card,
		links,
		primaryLinks,
		secondaryLinks: secondaryActions,
		meta,
		idBadge,
		healthIndicator,
		statusToggle,
		portalEntry,
		galleryEntry,
		playEntry,
		selectionOverlay
	};
};
var redistributeCardActions = (primaryLinks, links, secondaryLinks, controls, isPrimaryActionFn) => {
	const moreBtn = secondaryLinks.querySelector(".btn-more-actions");
	const moveToTarget = (btn, isPrimary, container) => {
		if (!btn) return;
		if (isPrimary) container.appendChild(btn);
		else links.appendChild(btn);
	};
	if (!isPrimaryActionFn("id") && !links.contains(controls.idBadge)) links.appendChild(controls.idBadge);
	if (controls.statusToggle?.element && !isPrimaryActionFn("viewed") && !links.contains(controls.statusToggle.element)) links.appendChild(controls.statusToggle.element);
	moveToTarget(controls.playEntry || primaryLinks.querySelector(`.${Config.CLASSES.btnPlayFullscreen}`) || links.querySelector(`.${Config.CLASSES.btnPlayFullscreen}`) || secondaryLinks.querySelector(`.${Config.CLASSES.btnPlayFullscreen}`), isPrimaryActionFn("play"), secondaryLinks);
	moveToTarget(controls.galleryEntry || primaryLinks.querySelector(".btn-open-gallery") || links.querySelector(".btn-open-gallery") || secondaryLinks.querySelector(".btn-open-gallery"), isPrimaryActionFn("preview"), secondaryLinks);
	moveToTarget(controls.portalEntry || primaryLinks.querySelector(".btn-external-links") || links.querySelector(".btn-external-links") || secondaryLinks.querySelector(".btn-external-links"), isPrimaryActionFn("external"), secondaryLinks);
	if (moreBtn instanceof HTMLElement) secondaryLinks.appendChild(moreBtn);
	moveToTarget(findMagnetButton(primaryLinks) || findMagnetButton(links) || findMagnetButton(secondaryLinks), isPrimaryActionFn("magnet"), secondaryLinks);
	if (moreBtn instanceof HTMLElement) {
		const hasOverflow = Array.from(links.children).some((child) => child.style.display !== "none");
		moreBtn.style.display = hasOverflow ? "" : "none";
	}
};
var DOUBLE_TAP_DELTA = 10;
var SWIPE_NAV_THRESHOLD = 50;
var GALLERY_SLIDESHOW_INTERVAL = 3e3;
var isGalleryImage = (preview) => preview?.type === "image";
var getGalleryWrappedIndex = (currentIndex, length, delta) => {
	if (length <= 0) return 0;
	return (currentIndex + delta + length) % length;
};
var buildGallerySearchUrl = (src) => EXTERNAL_URLS.GOOGLE_LENS.replace("{url}", encodeURIComponent(src));
var resolveGalleryTouchAction = ({ diffX, diffY, isZoomed, item, lastTapTime, now }) => {
	if (Boolean(item) && isGalleryImage(item) && now - lastTapTime < TIMING.UI_TRANSITION_NORMAL && Math.abs(diffX) < DOUBLE_TAP_DELTA && Math.abs(diffY) < DOUBLE_TAP_DELTA) return "toggleZoom";
	if (isZoomed) return "none";
	if (diffY > UI_CONSTANTS.SWIPE_DISMISS_THRESHOLD && Math.abs(diffX) < SWIPE_NAV_THRESHOLD) return "dismiss";
	if (Math.abs(diffX) > SWIPE_NAV_THRESHOLD && Math.abs(diffY) < SWIPE_NAV_THRESHOLD) return diffX > 0 ? "prev" : "next";
	return "none";
};
var createGalleryCounter = (total) => {
	const current = h("span", { className: "fc2-enh-viewer-counter-current" }, "1");
	return {
		counter: h("div", { className: "fc2-enh-viewer-counter" }, current, h("span", { className: "fc2-enh-viewer-counter-separator" }, "/"), h("span", {}, total.toString())),
		current
	};
};
var createGalleryThumbStrip = (previews, _currentIndex) => {
	const thumbItems = previews.map((preview, index) => {
		const el = preview.type === "image" ? h("img", {
			src: preview.src,
			loading: "eager"
		}) : h("video", {
			muted: true,
			src: preview.src
		});
		return h("div", {
			className: `fc2-enh-thumb-item ${index === _currentIndex ? "active" : ""}`,
			"data-idx": index
		}, el);
	});
	return {
		thumbItems,
		thumbStrip: h("div", { className: "fc2-enh-viewer-thumbs" }, ...thumbItems)
	};
};
var createGalleryNavigation = (previews) => {
	if (previews.length <= 1) return {
		navNext: null,
		navPrev: null
	};
	return {
		navPrev: h("div", { className: "fc2-enh-viewer-nav prev" }, h("span", {
			className: "fc2-icon fc2-enh-viewer-nav-icon",
			innerHTML: IconChevronLeft
		})),
		navNext: h("div", { className: "fc2-enh-viewer-nav next" }, h("span", {
			className: "fc2-icon fc2-enh-viewer-nav-icon",
			innerHTML: IconChevronRight
		}))
	};
};
var createGalleryElements = (previews, currentIndex) => {
	const container = h("div", { className: "fc2-enh-viewer-backdrop" });
	const closeBtn = h("div", { className: "fc2-enh-viewer-close" }, h("span", {
		className: "fc2-icon",
		innerHTML: IconXmark
	}));
	const btnSlideshow = h("button", {
		type: "button",
		className: "fc2-enh-viewer-action pb-play",
		title: t("gallerySlideshow"),
		innerHTML: IconPlay
	});
	const btnSearch = h("button", {
		type: "button",
		className: "fc2-enh-viewer-action pb-search",
		title: t("gallerySearch"),
		innerHTML: IconImageSearch
	});
	const actionBar = h("div", { className: "fc2-enh-viewer-actions" }, btnSlideshow, btnSearch);
	const { counter, current } = createGalleryCounter(previews.length);
	const stage = h("div", { className: "fc2-enh-viewer-stage" });
	const { thumbItems, thumbStrip } = createGalleryThumbStrip(previews, currentIndex);
	const { navNext, navPrev } = createGalleryNavigation(previews);
	if (previews.length <= 1) container.classList.add("fc2-enh-viewer-single");
	container.append(stage, closeBtn, actionBar, counter, thumbStrip);
	if (navPrev && navNext) container.append(navPrev, navNext);
	return {
		btnSearch,
		btnSlideshow,
		closeBtn,
		container,
		counterCurrent: current,
		navNext,
		navPrev,
		stage,
		thumbItems,
		thumbStrip
	};
};
var scrollActiveThumbIntoView = (thumbStrip) => {
	const activeThumb = thumbStrip.querySelector(".fc2-enh-thumb-item.active");
	if (activeThumb) activeThumb.scrollIntoView({
		behavior: "smooth",
		block: "nearest",
		inline: "center"
	});
};
var createGalleryCloseHandler = (refs, state, cleanupFns, getDeferredScrollTimer, clearDeferredScrollTimer, stopSlideshow, isActive, clearActive) => {
	let isClosed = false;
	return () => {
		if (isClosed) return;
		isClosed = true;
		const deferredScrollTimer = getDeferredScrollTimer();
		if (deferredScrollTimer !== null) {
			window.clearTimeout(deferredScrollTimer);
			clearDeferredScrollTimer();
		}
		stopSlideshow(state, refs.btnSlideshow);
		cleanupFns.forEach((cleanup) => cleanup());
		refs.container.remove();
		if (isActive()) clearActive();
	};
};
var attachThumbSelection = (refs, state, render) => {
	refs.thumbItems.forEach((thumbItem, index) => {
		thumbItem.onclick = (event) => {
			event.stopPropagation();
			if (index !== state.index) {
				state.isZoomed = false;
				state.index = index;
				render("init");
			}
		};
	});
};
var bindGalleryControls = (previews, refs, state, render, close, navigate, toggleSlideshow) => {
	refs.closeBtn.onclick = (event) => {
		event.stopPropagation();
		close();
	};
	refs.stage.onclick = (event) => {
		if (event.target === refs.stage) close();
	};
	refs.btnSlideshow.onclick = (event) => {
		event.stopPropagation();
		toggleSlideshow(state, refs.btnSlideshow, () => navigate("next"));
	};
	refs.btnSearch.onclick = (event) => {
		event.stopPropagation();
		const item = previews[state.index];
		if (isGalleryImage(item)) BrowserService.openTab(buildGallerySearchUrl(item.src));
	};
	attachThumbSelection(refs, state, render);
	if (refs.navPrev && refs.navNext) {
		refs.navPrev.onclick = (event) => {
			event.stopPropagation();
			navigate("prev");
		};
		refs.navNext.onclick = (event) => {
			event.stopPropagation();
			navigate("next");
		};
	}
};
var scheduleInitialThumbSync = (refs, close, setDeferredScrollTimer, isActive) => {
	setDeferredScrollTimer(window.setTimeout(() => {
		setDeferredScrollTimer(null);
		if (isActive(close)) scrollActiveThumbIntoView(refs.thumbStrip);
	}, 50));
};
var activeGalleryClose = null;
var createViewerState = (startIndex) => ({
	index: startIndex,
	isZoomed: false,
	isTransitioning: false,
	slideshowInterval: null,
	lastTapTime: 0,
	touchStartX: 0,
	touchStartY: 0
});
var preloadGalleryItems = (previews) => {
	previews.forEach((item) => {
		if (item.type === "image") {
			const image = new Image();
			image.src = item.src;
			return;
		}
		const video = document.createElement("video");
		video.preload = "metadata";
		video.src = item.src;
	});
};
var setSlideshowButtonState = (button, isActive) => {
	button.classList.toggle("active", isActive);
	button.textContent = "";
	const temp = document.createElement("div");
	temp.innerHTML = isActive ? IconPause : IconPlay;
	temp.querySelectorAll("script, style, [onload], [onerror]").forEach((el) => el.remove());
	while (temp.firstChild) button.appendChild(temp.firstChild);
};
var stopSlideshow = (state, button) => {
	if (state.slideshowInterval) {
		clearInterval(state.slideshowInterval);
		state.slideshowInterval = null;
	}
	setSlideshowButtonState(button, false);
};
var toggleSlideshow = (state, button, onAdvance) => {
	if (state.slideshowInterval) {
		stopSlideshow(state, button);
		return;
	}
	state.slideshowInterval = window.setInterval(onAdvance, GALLERY_SLIDESHOW_INTERVAL);
	setSlideshowButtonState(button, true);
};
var createGalleryStageMedia = (item, isZoomed, onToggleZoom) => {
	const el = item.type === "video" ? h("video", {
		controls: true,
		autoplay: true,
		loop: true,
		onclick: (event) => event.stopPropagation(),
		className: "fc2-enh-viewer-stage-media"
	}) : h("img", {
		draggable: false,
		onclick: (event) => {
			event.stopPropagation();
			onToggleZoom();
		},
		className: `fc2-enh-viewer-stage-image${isZoomed ? " zoomed" : ""}`
	});
	smartLoadMedia(el, item.src);
	return el;
};
var renderGalleryView = (previews, state, refs, render, direction = "init") => {
	if (state.isTransitioning) {
		state.pendingIndex = state.index;
		return;
	}
	const item = previews[state.index];
	if (!item) return;
	state.isTransitioning = true;
	refs.counterCurrent.textContent = (state.index + 1).toString();
	refs.btnSearch.classList.toggle("fc2-hidden", !isGalleryImage(item));
	refs.thumbItems.forEach((thumbItem, index) => thumbItem.classList.toggle("active", index === state.index));
	scrollActiveThumbIntoView(refs.thumbStrip);
	refs.stage.className = `fc2-enh-viewer-stage slide-${direction}${state.isZoomed ? " zoomed" : ""}`;
	refs.stage.textContent = "";
	refs.stage.appendChild(createGalleryStageMedia(item, state.isZoomed, () => {
		state.isZoomed = !state.isZoomed;
		render("init");
	}));
	window.setTimeout(() => {
		state.isTransitioning = false;
		const pendingIndex = state.pendingIndex;
		state.pendingIndex = void 0;
		if (pendingIndex !== void 0 && refs.container.isConnected) {
			state.index = pendingIndex;
			render("init");
		}
	}, TIMING.UI_TRANSITION_NORMAL);
};
var attachKeyboardNavigation = (previews, state, render, close) => {
	const keyHandler = (event) => {
		const key = event.key.toLowerCase();
		if (key === "a" || key === "arrowleft") {
			event.stopPropagation();
			state.isZoomed = false;
			state.index = getGalleryWrappedIndex(state.index, previews.length, -1);
			render("prev");
		} else if (key === "d" || key === "arrowright") {
			event.stopPropagation();
			state.isZoomed = false;
			state.index = getGalleryWrappedIndex(state.index, previews.length, 1);
			render("next");
		} else if (key === "escape") {
			event.stopPropagation();
			close();
		}
	};
	window.addEventListener("keydown", keyHandler);
	return () => window.removeEventListener("keydown", keyHandler);
};
var attachTouchNavigation = (container, previews, state, render, close) => {
	const onTouchStart = (event) => {
		const touch = event.changedTouches[0];
		if (!touch) return;
		state.touchStartX = touch.screenX;
		state.touchStartY = touch.screenY;
	};
	const onTouchEnd = (event) => {
		const touch = event.changedTouches[0];
		if (!touch) return;
		const diffX = touch.screenX - state.touchStartX;
		const diffY = touch.screenY - state.touchStartY;
		const now = Date.now();
		const action = resolveGalleryTouchAction({
			diffX,
			diffY,
			isZoomed: state.isZoomed,
			item: previews[state.index],
			lastTapTime: state.lastTapTime,
			now
		});
		state.lastTapTime = now;
		if (action === "toggleZoom") {
			event.preventDefault();
			state.isZoomed = !state.isZoomed;
			render("init");
			return;
		}
		if (action === "dismiss") {
			close();
			return;
		}
		if (action === "prev" || action === "next") {
			state.index = getGalleryWrappedIndex(state.index, previews.length, action === "prev" ? -1 : 1);
			render(action);
		}
	};
	container.addEventListener("touchstart", onTouchStart, { passive: true });
	container.addEventListener("touchend", onTouchEnd, { passive: false });
	return () => {
		container.removeEventListener("touchstart", onTouchStart);
		container.removeEventListener("touchend", onTouchEnd);
	};
};
var openGalleryViewer = (_id, previews, startIndex = 0) => {
	if (!previews.length) return;
	activeGalleryClose?.();
	const state = createViewerState(startIndex);
	preloadGalleryItems(previews);
	const refs = createGalleryElements(previews, state.index);
	const cleanupFns = [];
	let deferredScrollTimer = null;
	const close = createGalleryCloseHandler(refs, state, cleanupFns, () => deferredScrollTimer, () => {
		deferredScrollTimer = null;
	}, stopSlideshow, () => activeGalleryClose === close, () => {
		activeGalleryClose = null;
	});
	activeGalleryClose = close;
	const render = (direction = "init") => renderGalleryView(previews, state, refs, render, direction);
	const navigate = (direction) => {
		state.isZoomed = false;
		state.index = getGalleryWrappedIndex(state.index, previews.length, direction === "prev" ? -1 : 1);
		render(direction);
	};
	bindGalleryControls(previews, refs, state, render, close, navigate, toggleSlideshow);
	cleanupFns.push(attachKeyboardNavigation(previews, state, render, close));
	cleanupFns.push(attachTouchNavigation(refs.container, previews, state, render, close));
	scheduleInitialThumbSync(refs, close, (timer) => {
		deferredScrollTimer = timer;
	}, (candidateClose) => activeGalleryClose === candidateClose);
	render();
	UIHost.add(refs.container);
};
var log$11 = Logger.scope("OpenExtraGallery");
/**
* Single use-case: fetch extra previews (Paipancon etc.) and open the gallery lightbox.
* All UI entry points (card / detail / toolbar) must call this — no duplicate fetch+toast logic.
*/
async function openExtraGallery(id) {
	try {
		const res = await resolveService(SCRAPER_SERVICE).fetchExtraPreviews(id);
		if (res.items.length > 0) {
			openGalleryViewer(id, res.items);
			return "opened";
		}
		if (res.error === "cf-blocked") {
			Toast.show(t("alertPreviewCfBlocked"), "warn");
			return "cf-blocked";
		}
		if (res.error === "rate-limited") {
			Toast.show(t("alertPreviewRateLimited"), "warn");
			return "rate-limited";
		}
		Toast.show(t("alertNoPreview"), "info");
		return "empty";
	} catch (error) {
		log$11.warn("Extra gallery failed", error);
		Toast.show(t("alertPreviewLoadFailed"), "error");
		return "error";
	}
}
var log$10 = Logger.scope("OpenVideoPreview");
/**
* Single use-case: resolve video preview URL and open it in the gallery lightbox.
*/
async function openVideoPreview(id, type, previewSlug) {
	try {
		const preview = resolveService(PREVIEW_SERVICE);
		if (!await preview.checkExistence(id, type, previewSlug)) {
			Toast.show(t("alertNoVideoPreview"), "info");
			return "missing";
		}
		openGalleryViewer(id, [{
			type: "video",
			src: preview.getPreviewUrl(id, type, previewSlug)
		}]);
		return "opened";
	} catch (error) {
		log$10.warn("Video preview failed", error);
		Toast.show(t("alertPreviewLoadFailed"), "error");
		return "error";
	}
}
var getSelectionService = () => resolveService(SELECTION_SERVICE);
var hasCensoredIndicator = (preservedIconsHTML) => Boolean(preservedIconsHTML?.includes(PATTERNS.CENSORED_INDICATOR));
var applyCardVisualState = (card, state, classes) => {
	if (typeof state.isSearching === "boolean") if (state.isSearching) card.dataset.enhSearching = "true";
	else delete card.dataset.enhSearching;
	if (typeof state.hasMagnet === "boolean") {
		card.dataset.hasMagnet = String(state.hasMagnet);
		card.classList.toggle(classes.noMagnet || "fc2-no-magnet", !state.hasMagnet);
	}
	if (typeof state.isViewed === "boolean") card.classList.toggle(classes.isViewed, state.isViewed);
	if (typeof state.isBlocked === "boolean" && classes.isBlocked) card.classList.toggle(classes.isBlocked, state.isBlocked);
	if (typeof state.isCensored === "boolean" && classes.isCensored) card.classList.toggle(classes.isCensored, state.isCensored);
};
var updateMagnetState = ({ card, links, primaryLinks, secondaryLinks, state, options, classes, controls, isPrimaryActionFn }) => {
	const targetContainer = isPrimaryActionFn("magnet") ? secondaryLinks : links;
	if (state.hasMagnet && state.magnetUrl && !hasMagnetButton(targetContainer)) mountMagnetButton(targetContainer, state.magnetUrl);
	applyCardVisualState(card, { hasMagnet: state.hasMagnet }, classes);
	if (!options.skipFilters) GlobalFilterManager.applyCardVisibility(card, state.hasMagnet);
	redistributeCardActions(primaryLinks, links, secondaryLinks, controls, isPrimaryActionFn);
};
var handlePlayFullscreen = async (data, e) => {
	const btn = e.target.closest(".fc2-enh-btn");
	const iconContainer = btn?.querySelector(".fc2-icon");
	const originalIcon = iconContainer?.innerHTML || "";
	if (iconContainer) iconContainer.innerHTML = IconSpinner;
	if (btn) btn.classList.add(Config.CLASSES.btnLoading);
	try {
		await openVideoPreview(data.id, data.type, data.previewSlug || void 0);
	} finally {
		if (iconContainer) iconContainer.innerHTML = originalIcon || "<svg viewBox=\"0 0 384 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80v352c0 17.4 9.4 33.4 24.5 41.9S58.2 482 73 473l288-176c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41z\"/></svg>";
		if (btn) btn.classList.remove(Config.CLASSES.btnLoading);
	}
};
var handleOpenGallery = async (data, e) => {
	const btn = e.target.closest(".fc2-enh-btn");
	const iconContainer = btn?.querySelector(".fc2-icon");
	const originalIcon = iconContainer?.innerHTML || "";
	if (iconContainer) iconContainer.innerHTML = IconSpinner;
	if (btn) btn.classList.add(Config.CLASSES.btnLoading);
	try {
		await openExtraGallery(data.id);
	} finally {
		if (iconContainer) iconContainer.innerHTML = originalIcon || "<svg viewBox=\"0 0 576 512\" width=\"1.2em\" height=\"1.2em\" fill=\"currentColor\"><path fill=\"currentColor\" d=\"M160 32c-35.3 0-64 28.7-64 64v224c0 35.3 28.7 64 64 64h352c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64zm236 106.7l96 144c4.9 7.4 5.4 16.8 1.2 24.6S480.9 320 472 320H200c-9.2 0-17.6-5.3-21.6-13.6s-2.9-18.2 2.9-25.4l64-80c4.6-5.7 11.4-9 18.7-9s14.2 3.3 18.7 9l17.3 21.6l56-84c4.5-6.6 12-10.6 20-10.6s15.5 4 20 10.7M192 128a32 32 0 1 1 64 0a32 32 0 1 1-64 0m-144-8c0-13.3-10.7-24-24-24S0 106.7 0 120v224c0 75.1 60.9 136 136 136h320c13.3 0 24-10.7 24-24s-10.7-24-24-24H136c-48.6 0-88-39.4-88-88z\"/></svg>";
		if (btn) btn.classList.remove(Config.CLASSES.btnLoading);
	}
};
var attachCardPreviewListeners = (card, data) => {
	card.addEventListener("fc2-play-fullscreen", (e) => {
		handlePlayFullscreen(data, e);
	});
	card.addEventListener("fc2-open-gallery", (e) => {
		handleOpenGallery(data, e);
	});
};
var bindSelectionLogic = (data, card, checkbox, syncSelection) => {
	const unbindSelection = CoreEvents.on(AppEvents.SELECTION_CHANGED, () => {
		syncSelection();
	});
	const unbindMode = resolveService(STATE_SERVICE).on("isSelectionMode", (value) => {
		card.classList.toggle("fc2-selection-mode-active", !!value);
		if (!value) syncSelection();
	});
	checkbox.onchange = () => {
		getSelectionService().setSelection(data.id, checkbox.checked);
	};
	syncSelection();
	card.classList.toggle("fc2-selection-mode-active", !!resolveService(STATE_SERVICE).proxy.isSelectionMode);
	return () => {
		unbindSelection();
		unbindMode();
	};
};
var handleCardUpdate = (change, data, card, options, classes, controls, isPrimaryActionFn, links, primaryLinks, secondaryLinks, statusToggle) => {
	if (!change) return;
	const state = ViewStore.get(data.id);
	if (change.key === "isSearching") {
		applyCardVisualState(card, { isSearching: state.isSearching }, classes);
		if (!state.isSearching && !options.skipFilters) GlobalFilterManager.applyCardVisibility(card, state.hasMagnet);
	} else if (change.key === "hasMagnet" || change.key === "magnetUrl") updateMagnetState({
		card,
		links,
		primaryLinks,
		secondaryLinks,
		state,
		options,
		classes,
		controls,
		isPrimaryActionFn
	});
	else if (change.key === "status") {
		applyCardVisualState(card, { isViewed: String(change.value || "") === "watched" }, classes);
		if (!options.skipFilters) GlobalFilterManager.applyHistoryVisibility(card);
		statusToggle?.update?.(change);
	}
};
var applyInitialCardState = (card, data, initialState, options, classes) => {
	applyCardVisualState(card, {
		hasMagnet: initialState.hasMagnet,
		isSearching: initialState.isSearching,
		isViewed: resolveService(HISTORY_SERVICE).has(data.id, "watched"),
		isCensored: hasCensoredIndicator(data.preservedIconsHTML)
	}, classes);
	if (!options.skipFilters) {
		GlobalFilterManager.applyCardVisibility(card, initialState.hasMagnet);
		GlobalFilterManager.applyCensoredFilter(card);
		GlobalFilterManager.applyHistoryVisibility(card);
	}
};
var EnhancedCard = (data, options = {}) => {
	const classes = Config.CLASSES;
	const originalId = data.id;
	data.id = GlobalHistoryManager.normalizeId(originalId);
	const suppressNativeDetailMeta = Boolean(options.skipFilters);
	const handleSelectionToggle = (id) => {
		SelectionService.toggleSelection(id);
	};
	const { card, links, primaryLinks, secondaryLinks, statusToggle, idBadge, portalEntry, galleryEntry, playEntry, selectionOverlay } = createEnhancedCardView({
		classes,
		data,
		minimal: Boolean(options.minimal),
		suppressNativeDetailMeta,
		onCardClick: (event) => {
			if (resolveService(STATE_SERVICE).proxy.isSelectionMode) {
				event.preventDefault();
				event.stopPropagation();
				handleSelectionToggle(data.id);
				return;
			}
			if (!event.target.closest(`.${classes.resourceBtn}`)) GlobalHistoryManager.setWatched(data.id);
		}
	});
	const checkbox = selectionOverlay.querySelector("input");
	attachCardPreviewListeners(card, data);
	const unbindConfig = CoreEvents.on(AppEvents.CONFIG_CHANGED, ({ key }) => {
		if (key === "cardPrimaryActions") redistributeCardActions(primaryLinks, links, secondaryLinks, {
			idBadge,
			statusToggle,
			portalEntry,
			galleryEntry,
			playEntry
		}, isPrimaryAction);
	});
	redistributeCardActions(primaryLinks, links, secondaryLinks, {
		idBadge,
		statusToggle,
		portalEntry,
		galleryEntry,
		playEntry
	}, isPrimaryAction);
	const syncSelection = () => {
		const isSelected = SelectionService.isSelected(data.id);
		checkbox.checked = isSelected;
		card.classList.toggle("fc2-is-selected", isSelected);
	};
	const unbindSelection = bindSelectionLogic(data, card, checkbox, syncSelection);
	const initialState = ViewStore.get(data.id);
	applyInitialCardState(card, data, initialState, options, classes);
	if (initialState.hasMagnet && initialState.magnetUrl) updateMagnetState({
		card,
		links,
		primaryLinks,
		secondaryLinks,
		state: initialState,
		options,
		classes,
		controls: {
			idBadge,
			statusToggle,
			portalEntry,
			galleryEntry,
			playEntry
		},
		isPrimaryActionFn: isPrimaryAction
	});
	const destroyHooks = [];
	const component = {
		id: data.id,
		element: card,
		update: (raw) => {
			handleCardUpdate(raw, data, card, options, classes, {
				idBadge,
				statusToggle,
				portalEntry,
				galleryEntry,
				playEntry
			}, isPrimaryAction, links, primaryLinks, secondaryLinks, statusToggle);
		},
		addDestroyHook: (fn) => destroyHooks.push(fn),
		destroy: () => {
			unbindConfig();
			unbindSelection();
			statusToggle?.destroy?.();
			PreviewLogic.disposePreviewForCard(card);
			destroyHooks.forEach((hook) => {
				try {
					hook();
				} catch {}
			});
		}
	};
	ComponentRegistry.register(component);
	return {
		finalElement: card,
		linksContainer: links,
		primaryLinksContainer: primaryLinks,
		secondaryLinksContainer: secondaryLinks,
		newCard: card,
		component
	};
};
var UIBuilder = {
	createEnhancedCard: EnhancedCard,
	addMagnetButton: MagnetButton,
	addActressButton: ActressButton,
	addPlayButton: (container, id, type, previewSlug) => {
		if (!container || container.querySelector(`.${Config.CLASSES.btnPlayFullscreen}`)) return;
		const handlePlay = async (e) => {
			const target = e.currentTarget;
			if (target.classList.contains(Config.CLASSES.btnLoading)) return;
			target.classList.add(Config.CLASSES.btnLoading);
			try {
				await openVideoPreview(id, type, previewSlug);
			} finally {
				target.classList.remove(Config.CLASSES.btnLoading);
			}
		};
		const btn = Button(IconPlay, t("labelPlayPreview"), void 0, (e) => {
			handlePlay(e);
		}, Config.CLASSES.btnPlayFullscreen, {
			iconOnly: true,
			size: "micro"
		});
		container.appendChild(btn);
	},
	/**
	* Detail/toolbar fallback only. List cards use btn-open-gallery from EnhancedCard.
	* Click always opens extra gallery (never cover progressive video).
	*/
	addPreviewButton: (container, id) => {
		if (!container || container.querySelector(".btn-preview")) return;
		if ((container.closest(`.${Config.CLASSES.processedCard}`) || container).querySelector(".btn-open-gallery")) return;
		const handleGallery = async (e) => {
			const target = e.currentTarget;
			if (target.classList.contains(Config.CLASSES.btnLoading)) return;
			target.classList.add(Config.CLASSES.btnLoading);
			try {
				await openExtraGallery(id);
			} finally {
				target.classList.remove(Config.CLASSES.btnLoading);
			}
		};
		const btn = Button(IconImages, t("extraPreviewTitle"), void 0, (e) => {
			handleGallery(e);
		}, "btn-preview", {
			iconOnly: true,
			size: "micro"
		});
		container.appendChild(btn);
	},
	createDetailToolbar: UIToolbar.createDetailToolbar,
	createElement: (tag) => document.createElement(tag)
};
var log$9 = Logger.scope("Reactive");
var ReactiveController = exports("R",class ReactiveController {
	bindings = /* @__PURE__ */ new Map();
	cleanups = [];
	disposed = false;
	constructor() {
		const unbind = resolveService(STATE_SERVICE).on(({ prop, value }) => {
			if (this.disposed) return;
			this.bindings.forEach((options, el) => {
				if (options.key === prop) this.updateElement(el, options, value);
			});
		});
		this.cleanups.push(unbind);
	}
	get isDisposed() {
		return this.disposed;
	}
	bind(el, options) {
		if (this.disposed) {
			log$9.warn("Attempted to bind on a disposed controller");
			return;
		}
		this.bindings.set(el, options);
		const currentValue = resolveService(STATE_SERVICE).get(options.key);
		this.updateElement(el, options, currentValue);
		const inputEl = el;
		const eventType = el.tagName === "TEXTAREA" || el.tagName === "INPUT" && [
			"text",
			"password",
			"url",
			"email",
			"number",
			"range"
		].includes(el.type) ? "input" : "change";
		const listener = () => {
			if (this.disposed) return;
			let newValue;
			if (options.mode === "checked") newValue = el.checked;
			else if (options.mode === "toggle") newValue = !resolveService(STATE_SERVICE).get(options.key);
			else {
				const raw = inputEl.value;
				newValue = typeof resolveService(STATE_SERVICE).get(options.key) === "number" ? Number(raw) : raw;
			}
			resolveService(STATE_SERVICE).set(options.key, newValue);
			if (options.onChange) options.onChange(newValue);
		};
		el.addEventListener(eventType, listener);
		this.cleanups.push(() => el.removeEventListener(eventType, listener));
	}
	updateElement(el, options, value) {
		const inputEl = el;
		switch (options.mode) {
			case "checked":
				el.checked = !!value;
				break;
			case "value":
				inputEl.value = String(value ?? "");
				break;
			case "text":
				el.textContent = String(value ?? "");
				break;
			case "html": {
				const { html, droppedElements, droppedAttributes } = sanitizeHtmlDetailed(String(value ?? ""));
				if (droppedElements > 0 || droppedAttributes > 0) log$9.warn(`Sanitized unsafe html binding (removed ${droppedElements} element(s), ${droppedAttributes} attribute(s))`);
				el.innerHTML = html;
				break;
			}
			default: inputEl.value = String(value ?? "");
		}
	}
	bindList(container, options) {
		if (this.disposed) return;
		const render = () => {
			const list = resolveService(STATE_SERVICE).get(options.key) || [];
			container.textContent = "";
			list.forEach((item, index) => {
				container.appendChild(options.renderItem(item, index));
			});
		};
		const unbind = resolveService(STATE_SERVICE).on(({ prop }) => {
			if (this.disposed) return;
			if (prop === options.key) render();
		});
		this.cleanups.push(unbind);
		render();
	}
	listen(key, callback) {
		if (this.disposed) return;
		const unbind = resolveService(STATE_SERVICE).on(({ prop, value }) => {
			if (this.disposed) return;
			if (prop === key) callback(value);
		});
		this.cleanups.push(unbind);
		callback(resolveService(STATE_SERVICE).get(key));
	}
	dispose() {
		if (this.disposed) return;
		this.disposed = true;
		this.cleanups.forEach((fn) => fn());
		this.cleanups = [];
		this.bindings.clear();
	}
	static create() {
		return new ReactiveController();
	}
});
var log$8 = Logger.scope("CardPipeline");
var markSearching = (ctx) => {
	ViewStore.set(ctx.id, "isSearching", true);
	return ctx;
};
var buildUI = (ctx) => {
	const result = UIBuilder.createEnhancedCard({
		...ctx.data,
		...ctx.extraUi
	});
	ctx.el = result.finalElement;
	ViewStore.set(ctx.id, "newCard", result.newCard);
	return ctx;
};
var runPostProcess = (ctx) => {
	if (ctx.postProcess && ctx.el) ctx.postProcess(ctx.el, ViewStore.get(ctx.id).newCard, Config.CLASSES);
	return ctx;
};
var replaceDom = (ctx) => {
	if (ctx.el) ctx.host.replaceChildren(ctx.el);
	return ctx;
};
var applyFilters = (ctx) => {
	const newCard = ViewStore.get(ctx.id).newCard;
	if (newCard) {
		if (newCard.classList.contains(Config.CLASSES.isCensored)) ctx.host.classList.add(Config.CLASSES.isCensored);
		if (newCard.classList.contains(Config.CLASSES.isViewed)) ctx.host.classList.add(Config.CLASSES.isViewed);
	}
	const state = ViewStore.get(ctx.id);
	GlobalFilterManager.applyCardVisibility(ctx.host, state.hasMagnet || !ctx.flags.enableMagnets);
	GlobalFilterManager.applyCensoredFilter(ctx.host);
	GlobalFilterManager.applyHistoryVisibility(ctx.host);
	return ctx;
};
var attachGalleryPreview = (ctx) => {
	return ctx;
};
var triggerMagnetLookup = (ctx) => {
	if (ctx.flags.enableMagnets) queueMagnetLookup(ctx.id, ctx.type, "card-ready");
	else handleMagnetResult(ctx.id, null);
	return ctx;
};
var stages = [
	markSearching,
	buildUI,
	runPostProcess,
	replaceDom,
	applyFilters,
	attachGalleryPreview,
	triggerMagnetLookup
];
var GlobalCardLazyLoader = new class CardLazyLoader {
	observer = null;
	observedElements = /* @__PURE__ */ new WeakSet();
	pendingTasks = /* @__PURE__ */ new Map();
	static LOAD_DELAY_MS = 150;
	resolveCardTargets(cardEl) {
		const C = Config.CLASSES;
		const processedCard = cardEl.classList.contains(C.processedCard) ? cardEl : cardEl.querySelector(`.${C.processedCard}`);
		return {
			hostCard: cardEl.classList.contains(C.processedCard) ? cardEl.parentElement?.closest(`.${C.cardRebuilt}:not(.${C.processedCard})`) : cardEl,
			processedCard
		};
	}
	init() {
		this.observer?.disconnect();
		this.observedElements = /* @__PURE__ */ new WeakSet();
		this.pendingTasks.forEach((t) => window.clearTimeout(t));
		this.pendingTasks.clear();
		this.observer = new IntersectionObserver((entries) => {
			entries.forEach((entry) => {
				const el = entry.target;
				if (this.pendingTasks.has(el)) window.clearTimeout(this.pendingTasks.get(el));
				if (entry.isIntersecting) {
					const timeoutId = window.setTimeout(() => {
						this.pendingTasks.delete(el);
						this.applyFilters(el);
						GlobalFilterManager.requestRefresh();
						this.observer?.unobserve(el);
						this.observedElements.delete(el);
					}, CardLazyLoader.LOAD_DELAY_MS);
					this.pendingTasks.set(el, timeoutId);
				}
			});
		}, { rootMargin: "200px" });
	}
	disconnect() {
		this.observer?.disconnect();
		this.observer = null;
		this.observedElements = /* @__PURE__ */ new WeakSet();
		this.pendingTasks.forEach((t) => window.clearTimeout(t));
		this.pendingTasks.clear();
	}
	observe(el) {
		if (this.observedElements.has(el)) return;
		this.observer?.observe(el);
		this.observedElements.add(el);
	}
	applyFilters(cardEl) {
		const C = Config.CLASSES;
		const id = cardEl.dataset.id;
		const { hostCard, processedCard } = this.resolveCardTargets(cardEl);
		if (resolveService(STATE_SERVICE).proxy.enableHistory && id) {
			const isViewed = resolveService(HISTORY_SERVICE).has(id, "watched");
			const isDownloaded = resolveService(HISTORY_SERVICE).has(id, "downloaded");
			const status = isViewed ? "watched" : isDownloaded ? "downloaded" : "none";
			if (ViewStore.get(id).status !== status) ViewStore.set(id, "status", status);
			hostCard?.classList.toggle(C.isViewed, isViewed);
			processedCard?.classList.toggle(C.isViewed, isViewed);
		}
		GlobalFilterManager.applyHistoryVisibility(hostCard || cardEl);
		const hasMagnet = hasMagnetButton(processedCard || cardEl) || (processedCard || cardEl).dataset.hasMagnet === "true";
		GlobalFilterManager.applyCardVisibility(hostCard || cardEl, hasMagnet);
		GlobalFilterManager.applyCensoredFilter(hostCard || cardEl);
	}
	setupLazyResources(_container, _id, _type, _el) {}
}();
var snapshotFlags = (state) => ({
	enableMagnets: !!state.proxy.enableMagnets,
	enableHistory: !!state.proxy.enableHistory
});
var runCardPipeline = (payload) => {
	const flags = snapshotFlags(resolveService(STATE_SERVICE));
	const ctx = {
		id: payload.id,
		type: payload.type,
		data: payload.data,
		host: payload.host,
		el: null,
		extraUi: payload.extraUi,
		flags,
		postProcess: payload.postProcess
	};
	for (const stage of stages) try {
		stage(ctx);
	} catch (error) {
		log$8.error(`Stage failed for card ${ctx.id}`, error);
		break;
	}
};
var log$7 = Logger.scope("CardEnhancement");
var CardEnhancementService = class {
	initialized = false;
	cleanupFns = [];
	init() {
		if (this.initialized) return;
		this.initialized = true;
		this.cleanupFns.push(CoreEvents.on(AppEvents.CARD_DISCOVERED, (payload) => {
			runCardPipeline(payload);
		}));
		this.cleanupFns.push(CoreEvents.on(AppEvents.VIEW_REFRESH_REQUESTED, ({ reason }) => {
			log$7.info(`Refresh requested: ${reason}`);
		}));
	}
	destroy() {
		if (!this.initialized && this.cleanupFns.length === 0) return;
		this.initialized = false;
		this.cleanupFns.splice(0).forEach((cleanup) => cleanup());
	}
};
/** Constructed once from app/modules (composition root). */
function createCardEnhancementService() {
	return new CardEnhancementService();
}
var getPrimaryActionKeys = (override) => Array.isArray(override) ? override : Array.isArray(resolveService(STATE_SERVICE).proxy.cardPrimaryActions) ? resolveService(STATE_SERVICE).proxy.cardPrimaryActions : [
	"id",
	"viewed",
	"magnet",
	"external"
];
var resolveDetailActionContainers = (_primaryLinksContainer, secondaryLinksContainer, primaryActionsOverride) => {
	const primaryActions = getPrimaryActionKeys(primaryActionsOverride);
	const visibleActionContainer = _primaryLinksContainer;
	return {
		magnetContainer: primaryActions.includes("magnet") ? visibleActionContainer : secondaryLinksContainer,
		previewContainer: primaryActions.includes("preview") ? visibleActionContainer : secondaryLinksContainer
	};
};
var createDetailCardMount = (data, options = {}, primaryActionsOverride) => {
	const { finalElement, linksContainer, primaryLinksContainer, secondaryLinksContainer } = UIBuilder.createEnhancedCard(data, {
		...options,
		skipFilters: true
	});
	finalElement.classList.add("fc2-is-detail");
	return {
		finalElement,
		linksContainer,
		primaryLinksContainer,
		secondaryLinksContainer,
		...resolveDetailActionContainers(primaryLinksContainer, secondaryLinksContainer, primaryActionsOverride)
	};
};
var attachFetchedDetailMagnet = async (id, type, magnetContainer, bindings = {}) => {
	const fetchMagnet = bindings.fetchMagnet || resolveService(MAGNET_SERVICE).fetchMagnet.bind(resolveService(MAGNET_SERVICE));
	const addMagnetButton = bindings.addMagnetButton || UIBuilder.addMagnetButton;
	const url = await fetchMagnet(id, type);
	if (!url || !url.startsWith("magnet:?")) return;
	addMagnetButton(magnetContainer, url);
};
var attachDetailPreviewButton = (id, type, previewContainer, bindings = {}) => {
	const addPreviewButton = bindings.addPreviewButton || UIBuilder.addPreviewButton;
	const loadExtraPreviews = typeof bindings.loadExtraPreviews === "boolean" ? bindings.loadExtraPreviews : resolveService(STATE_SERVICE).proxy.loadExtraPreviews;
	if (type === "fc2" && previewContainer.querySelector(".btn-open-gallery")) return;
	if (type === "fc2" && loadExtraPreviews) addPreviewButton(previewContainer, id);
};
var normalizeDetailCardHost = (host, options) => {
	host.style.display = "block";
	host.style.width = options.width ?? "100%";
	host.style.padding = "0";
	host.style.margin = "0";
	host.style.lineHeight = "0";
	host.style.background = "transparent";
	host.style.height = "auto";
	host.style.overflow = "visible";
	if (options.maxWidth) host.style.maxWidth = options.maxWidth;
	if (!options.hideExistingChildren) return;
	Array.from(host.children).forEach((child) => {
		if (child instanceof HTMLElement) child.style.display = "none";
	});
};
var mountDetailCardEnhancement = (host, data, options = {}) => {
	const mount = createDetailCardMount(data, options.options, options.primaryActionsOverride);
	normalizeDetailCardHost(host, options);
	if (host.tagName === "IMG") host.replaceWith(mount.finalElement);
	else if (options.hideExistingChildren) host.appendChild(mount.finalElement);
	else host.replaceChildren(mount.finalElement);
	return mount;
};
var log$6 = Logger.scope("DetailOrchestrator");
var DetailOrchestrator = class {
	static MOUNT_ATTR = "data-enh-detail-mounted";
	static initialized = false;
	static cleanupFns = [];
	static init() {
		if (this.initialized) return;
		this.initialized = true;
		this.cleanupFns.push(CoreEvents.on(AppEvents.DETAIL_ENHANCEMENT_REQUESTED, ({ siteName, config, observer }) => {
			this.enhance(siteName, config, observer);
		}));
	}
	static destroy() {
		if (!this.initialized && this.cleanupFns.length === 0) return;
		this.initialized = false;
		this.cleanupFns.splice(0).forEach((cleanup) => cleanup());
	}
	static async enhance(siteName, config, observer) {
		try {
			const anchor = config.anchorElement || (config.anchorSelector ? document.querySelector(config.anchorSelector) : null);
			if (!anchor) return;
			const info = config.idExtractor();
			if (!info) return;
			const mountId = `${info.type}:${info.id}`;
			if (anchor.getAttribute(this.MOUNT_ATTR) === mountId) return;
			const metadata = config.metadataExtractor(info.id, info.type);
			const defaultTitle = info.type === "fc2" ? `FC2-PPV-${info.id}` : `${String(info.type).toUpperCase()}-${info.id}`;
			const cardData = {
				id: info.id,
				type: info.type,
				title: metadata.title || defaultTitle,
				...metadata
			};
			let containers;
			if (config.mode === "card") {
				const result = mountDetailCardEnhancement(anchor, cardData, config.mountOptions);
				containers = {
					mainElement: result.finalElement,
					magnetContainer: result.magnetContainer,
					previewContainer: result.previewContainer,
					linksContainer: result.linksContainer
				};
			} else {
				const toolbar = UIBuilder.createDetailToolbar({
					id: cardData.id,
					type: cardData.type,
					title: cardData.title || "",
					actress: cardData.actress,
					previewSlug: cardData.previewSlug || void 0
				}, UIBuilder.addPreviewButton, UIBuilder.addPlayButton);
				toolbar.setAttribute("data-enh-site-toolbar", siteName.toLowerCase());
				toolbar.setAttribute("data-enh-detail-id", mountId);
				anchor.after(toolbar);
				containers = {
					mainElement: toolbar,
					magnetContainer: toolbar,
					previewContainer: toolbar,
					linksContainer: toolbar.querySelector(`.${Config.CLASSES.infoArea}`)
				};
			}
			anchor.setAttribute(this.MOUNT_ATTR, mountId);
			if (!config.skipDefaultServices) this.attachServices(info.id, info.type, containers);
			if (config.onMounted) {
				const api = {
					addActressButton: UIBuilder.addActressButton,
					addMagnetButton: UIBuilder.addMagnetButton,
					addPreviewButton: UIBuilder.addPreviewButton,
					attachFetchedMagnet: (id, type, container) => attachFetchedDetailMagnet(id, type, container),
					attachDetailPreviewButton: (id, type, container) => attachDetailPreviewButton(id, type, container)
				};
				await config.onMounted(info.id, info.type, containers, api);
			}
			if (observer) observer.disconnect();
		} catch (e) {
			log$6.warn("Detail enhancement failed", {
				siteName,
				error: e
			});
		}
	}
	static attachServices(id, type, containers) {
		const videoType = type;
		attachDetailPreviewButton(id, videoType, containers.previewContainer, { loadExtraPreviews: resolveService(STATE_SERVICE).proxy.loadExtraPreviews });
		attachFetchedDetailMagnet(id, videoType, containers.magnetContainer);
	}
};
var GlobalClick = {
	initialized: false,
	clickHandler: null,
	init() {
		if (this.initialized) return;
		this.initialized = true;
		this.clickHandler = (e) => {
			if (!e.target.closest(`.${CSS_CLASSES.enhDropdown}`)) document.querySelectorAll(`.${CSS_CLASSES.enhDropdown}.active`).forEach((d) => {
				d.classList.remove("active");
				const card = d.closest(`.${CSS_CLASSES.processedCard}`) || d.closest(`.${CSS_CLASSES.cardRebuilt}`);
				if (card) card.classList.remove("fc2-has-active-dropdown");
			});
		};
		document.addEventListener("click", this.clickHandler);
	},
	destroy() {
		if (this.clickHandler) {
			document.removeEventListener("click", this.clickHandler);
			this.clickHandler = null;
		}
		this.initialized = false;
	}
};
var SmartTooltips = {
	currentTooltip: null,
	hideTimeout: null,
	initialized: false,
	mouseOverHandler: null,
	mouseOutHandler: null,
	resolveTooltipTarget(target) {
		if (!(target instanceof HTMLElement)) return null;
		return target.closest("[data-tooltip], [data-title]");
	},
	init() {
		if (this.initialized) return;
		this.initialized = true;
		this.mouseOverHandler = (e) => {
			const target = e.target;
			if (!(target instanceof Element)) return;
			const tooltipTarget = this.resolveTooltipTarget(target);
			const tooltipText = tooltipTarget?.getAttribute("data-tooltip") || tooltipTarget?.getAttribute("data-title");
			if (tooltipTarget && tooltipText && this.shouldShowTooltip(tooltipTarget)) this.show(tooltipTarget, tooltipText);
		};
		this.mouseOutHandler = (e) => {
			const target = e.target;
			if (!(target instanceof Element)) return;
			const tooltipTarget = this.resolveTooltipTarget(target);
			if (!tooltipTarget) return;
			const related = e.relatedTarget instanceof Element ? this.resolveTooltipTarget(e.relatedTarget) : null;
			if (related && related === tooltipTarget) return;
			this.hide();
		};
		document.addEventListener("mouseover", this.mouseOverHandler, true);
		document.addEventListener("mouseout", this.mouseOutHandler, true);
	},
	shouldShowTooltip(element) {
		if ("ontouchstart" in window) return false;
		if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") return false;
		if (element.querySelector(".tooltip")) return false;
		return true;
	},
	show(element, text) {
		this.hide(true);
		const tooltip = h("div", {
			className: "smart-tooltip",
			style: {
				position: "fixed",
				background: "var(--fc2-surface-float)",
				color: "var(--fc2-text-bright)",
				padding: "var(--fc2-space-xs) var(--fc2-space-md)",
				borderRadius: "var(--fc2-radius-md)",
				fontSize: "var(--fc2-text-xs)",
				zIndex: "100000",
				pointerEvents: "none",
				whiteSpace: "normal",
				maxWidth: "min(300px, calc(100vw - var(--fc2-space-md)))",
				overflowWrap: "anywhere",
				wordBreak: "break-word",
				lineHeight: "var(--fc2-line-base)",
				backdropFilter: "blur(var(--fc2-blur))",
				boxShadow: "var(--fc2-shadow-md)",
				border: "var(--fc2-border-width) solid var(--fc2-border)",
				opacity: "0",
				willChange: "transform, opacity",
				transition: "opacity var(--fc2-motion-fast) var(--fc2-ease-standard), transform var(--fc2-motion-fast) var(--fc2-ease-standard)",
				transform: "translateY(calc(var(--fc2-space-xs) + var(--fc2-border-width)))"
			}
		}, text);
		UIHost.add(tooltip);
		const rect = element.getBoundingClientRect();
		const tooltipRect = tooltip.getBoundingClientRect();
		let top = rect.bottom + 8;
		let left = rect.left + rect.width / 2 - tooltipRect.width / 2;
		if (left < 8) left = 8;
		if (left + tooltipRect.width > window.innerWidth - 8) left = window.innerWidth - tooltipRect.width - 8;
		if (top + tooltipRect.height > window.innerHeight - 8) top = rect.top - tooltipRect.height - 8;
		if (top < 8) top = 8;
		tooltip.style.top = `${top}px`;
		tooltip.style.left = `${left}px`;
		requestAnimationFrame(() => {
			tooltip.style.opacity = "1";
		});
		this.currentTooltip = tooltip;
	},
	hide(immediate = false) {
		const tooltip = this.currentTooltip;
		if (!tooltip) return;
		this.currentTooltip = null;
		if (this.hideTimeout !== null) {
			window.clearTimeout(this.hideTimeout);
			this.hideTimeout = null;
		}
		if (immediate) {
			tooltip.remove();
			return;
		}
		tooltip.style.opacity = "0";
		this.hideTimeout = window.setTimeout(() => {
			tooltip.remove();
			this.hideTimeout = null;
		}, 200);
	},
	destroy() {
		this.hide(true);
		if (this.hideTimeout !== null) {
			window.clearTimeout(this.hideTimeout);
			this.hideTimeout = null;
		}
		if (this.mouseOverHandler) {
			document.removeEventListener("mouseover", this.mouseOverHandler, true);
			this.mouseOverHandler = null;
		}
		if (this.mouseOutHandler) {
			document.removeEventListener("mouseout", this.mouseOutHandler, true);
			this.mouseOutHandler = null;
		}
		this.initialized = false;
	}
};
var log$5 = Logger.scope("UIManager");
var UIManager = class {
	cleanupFns = [];
	visibilityTimer = null;
	onInit() {
		this.onCleanup();
		log$5.debug("Initializing UI interactions");
		SmartTooltips.init();
		GlobalClick.init();
		resolveService(CARD_ENHANCEMENT_SERVICE).init?.();
		DetailOrchestrator.init();
		GlobalCardLazyLoader.init();
		PreviewLogic.registerContainer(document.body);
		this.bindStateListeners();
		this.bindEventListeners();
		this.updateGlobalClasses();
		this.applyAccentColor();
		this.updateVisibilityForCards();
	}
	scanAndTriggerInitialSearches(options = {}) {
		if (!resolveService(STATE_SERVICE).proxy.enableMagnets) return;
		const { includeSettledNoMagnet = false } = options;
		const C = Config.CLASSES;
		document.querySelectorAll(`.${C.cardRebuilt}`).forEach((card) => {
			const processed = card.querySelector(`.${C.processedCard}`);
			if (!processed) return;
			const { id, type } = processed.dataset;
			const hasMagnet = processed.dataset.hasMagnet === "true";
			const isSearching = processed.dataset.enhSearching === "true";
			if (id && type && (includeSettledNoMagnet ? !hasMagnet : isSearching && !hasMagnet)) queueMagnetLookup(id, type, includeSettledNoMagnet ? "enable-magnets" : "site-ready-scan");
		});
	}
	onCleanup() {
		GlobalCardLazyLoader.disconnect();
		if (this.visibilityTimer) {
			clearTimeout(this.visibilityTimer);
			this.visibilityTimer = null;
		}
		this.cleanupFns.splice(0).forEach((cleanup) => {
			cleanup();
		});
		resolveService(CARD_ENHANCEMENT_SERVICE).destroy?.();
		DetailOrchestrator.destroy();
		SmartTooltips.destroy();
		GlobalClick.destroy();
	}
	bindStateListeners() {
		this.cleanupFns.push(resolveService(STATE_SERVICE).on("showViewedBtn", () => this.updateGlobalClasses()));
		this.cleanupFns.push(resolveService(STATE_SERVICE).on("showIdBadge", () => this.updateGlobalClasses()));
		this.cleanupFns.push(resolveService(STATE_SERVICE).on("cardDensity", () => this.updateGlobalClasses()));
		this.cleanupFns.push(resolveService(STATE_SERVICE).on("accentColor", () => this.applyAccentColor()));
		this.cleanupFns.push(resolveService(STATE_SERVICE).on("enableMagnets", (value) => {
			this.updateVisibilityForCards();
			if (value) this.scanAndTriggerInitialSearches({ includeSettledNoMagnet: true });
		}));
		[
			"hideViewed",
			"hideNoMagnet",
			"hideCensored"
		].forEach((prop) => {
			this.cleanupFns.push(resolveService(STATE_SERVICE).on(prop, () => this.updateVisibilityForCards()));
		});
	}
	bindEventListeners() {
		this.cleanupFns.push(CoreEvents.on(AppEvents.MAGNET_FOUND, (data) => handleMagnetResult(data.id, data.url)));
		this.cleanupFns.push(CoreEvents.on(AppEvents.MAGNET_FAILED, (data) => handleMagnetResult(data.id, null)));
		this.cleanupFns.push(CoreEvents.on(AppEvents.HISTORY_ADDED, (data) => this.syncUIWithHistory(data.id, data.status)));
		this.cleanupFns.push(CoreEvents.on(AppEvents.HISTORY_REMOVED, (data) => this.syncUIWithHistory(data.id, "none")));
		this.cleanupFns.push(CoreEvents.on(AppEvents.HISTORY_LOADED, () => this.updateVisibilityForCards()));
		this.cleanupFns.push(CoreEvents.on(AppEvents.SITE_READY, () => {
			GlobalCardLazyLoader.init();
			this.updateVisibilityForCards();
			this.scanAndTriggerInitialSearches();
		}));
	}
	updateGlobalClasses() {
		const isShowViewed = resolveService(STATE_SERVICE).proxy.showViewedBtn !== false;
		const isShowId = resolveService(STATE_SERVICE).proxy.showIdBadge !== false;
		document.body.classList.toggle("fc2-hide-viewed-btn", !isShowViewed);
		document.body.classList.toggle("fc2-hide-id-badge", !isShowId);
		const density = resolveService(STATE_SERVICE).proxy.cardDensity;
		document.body.classList.toggle("fc2-card-density-cover-only", density === "cover-only");
		document.body.classList.toggle("fc2-card-density-compact", density === "compact");
		document.body.classList.toggle("fc2-card-density-standard", density === "standard");
		document.body.classList.toggle("fc2-card-density-immersive", density === "immersive");
	}
	applyAccentColor() {
		const color = resolveService(STATE_SERVICE).proxy.accentColor || "#ffffff";
		const rgb = hexToRgb(color);
		document.documentElement.style.setProperty("--fc2-primary", color);
		document.documentElement.style.setProperty("--fc2-primary-rgb", rgb);
	}
	updateVisibilityForCards() {
		if (this.visibilityTimer) clearTimeout(this.visibilityTimer);
		this.visibilityTimer = setTimeout(() => {
			this.visibilityTimer = null;
			const C = Config.CLASSES;
			document.querySelectorAll(`.${C.cardRebuilt}`).forEach((card) => {
				const cardEl = card;
				GlobalCardLazyLoader.observe(cardEl);
				GlobalCardLazyLoader.applyFilters(cardEl);
			});
			GlobalFilterManager.requestRefresh();
		}, 50);
	}
	syncUIWithHistory(id, status) {
		ViewStore.set(IdNormalizer.normalize(id), "status", status);
		GlobalFilterManager.requestRefresh();
	}
};
var UIManagerInstance = new UIManager();
var DragManager = class {
	container;
	trigger;
	onDragEndCallback;
	documentCleanup = [];
	currentConfig = {
		anchorX: "right",
		x: 20,
		anchorY: "bottom",
		y: 40
	};
	isDragging = false;
	rafId = null;
	snapTimer = null;
	hasMoved = false;
	startX = 0;
	startY = 0;
	startLeft = 0;
	startTop = 0;
	constructor(container, trigger, onDragEndCallback) {
		this.container = container;
		this.trigger = trigger;
		this.onDragEndCallback = onDragEndCallback;
		this.init();
	}
	init() {
		const savedConfig = typeof GM_getValue !== "undefined" ? GM_getValue(STORAGE_KEYS.FAB_POSITION) : null;
		if (savedConfig) try {
			this.applyAnchor(JSON.parse(savedConfig));
		} catch {
			this.applyAnchor(this.currentConfig);
		}
		else this.applyAnchor(this.currentConfig);
		this.trigger.addEventListener("mousedown", this.onDragStart);
		this.trigger.addEventListener("touchstart", this.onDragStart, { passive: false });
		document.addEventListener("mousemove", this.onDragMove, { passive: false });
		document.addEventListener("touchmove", this.onDragMove, { passive: false });
		document.addEventListener("mouseup", this.onDragEnd);
		document.addEventListener("touchend", this.onDragEnd);
		this.documentCleanup.push(() => document.removeEventListener("mousemove", this.onDragMove));
		this.documentCleanup.push(() => document.removeEventListener("touchmove", this.onDragMove));
		this.documentCleanup.push(() => document.removeEventListener("mouseup", this.onDragEnd));
		this.documentCleanup.push(() => document.removeEventListener("touchend", this.onDragEnd));
		const onResize = () => this.applyAnchor(this.currentConfig);
		window.addEventListener("resize", onResize);
		this.documentCleanup.push(() => window.removeEventListener("resize", onResize));
	}
	applyAnchor(config) {
		this.currentConfig = config;
		this.container.style.transition = "none";
		this.container.style.transform = "translate3d(0,0,0)";
		this.container.style.left = config.anchorX === "left" ? `${config.x}px` : "auto";
		this.container.style.right = config.anchorX === "right" ? `${config.x}px` : "auto";
		this.container.style.top = config.anchorY === "top" ? `${config.y}px` : "auto";
		this.container.style.bottom = config.anchorY === "bottom" ? `${config.y}px` : "auto";
	}
	getClientPos(e) {
		if ("touches" in e && e.touches.length > 0) {
			const touch = e.touches[0];
			return touch ? {
				x: touch.clientX,
				y: touch.clientY
			} : {
				x: 0,
				y: 0
			};
		}
		const mEvent = e;
		return {
			x: mEvent.clientX,
			y: mEvent.clientY
		};
	}
	onDragStart = (e) => {
		if ("button" in e && e.button !== 0) return;
		this.isDragging = true;
		this.hasMoved = false;
		const pos = this.getClientPos(e);
		this.startX = pos.x;
		this.startY = pos.y;
		const rect = this.container.getBoundingClientRect();
		this.startLeft = rect.left;
		this.startTop = rect.top;
		this.container.style.transition = "none";
		this.container.style.right = "auto";
		this.container.style.bottom = "auto";
		this.container.style.left = `${this.startLeft}px`;
		this.container.style.top = `${this.startTop}px`;
		this.container.style.transform = "translate3d(0,0,0)";
		e.stopPropagation();
	};
	onDragMove = (e) => {
		if (!this.isDragging) return;
		if (e.cancelable) e.preventDefault();
		const pos = this.getClientPos(e);
		const dx = pos.x - this.startX;
		const dy = pos.y - this.startY;
		if (Math.abs(dx) > 5 || Math.abs(dy) > 5) this.hasMoved = true;
		this.rafId = requestAnimationFrame(() => {
			this.rafId = null;
			if (!this.isDragging) return;
			this.container.style.transform = `translate3d(${dx}px, ${dy}px, 0)`;
		});
	};
	onDragEnd = () => {
		if (!this.isDragging) return;
		this.isDragging = false;
		const rect = this.container.getBoundingClientRect();
		this.container.style.transform = "translate3d(0,0,0)";
		if (this.hasMoved) {
			const isRight = rect.left + rect.width / 2 > window.innerWidth / 2;
			const isBottom = rect.top + rect.height / 2 > window.innerHeight / 2;
			const targetMargin = 20;
			const targetTopPx = Math.min(window.innerHeight - rect.height - targetMargin, Math.max(targetMargin, rect.top));
			const targetLeftPx = isRight ? window.innerWidth - rect.width - targetMargin : targetMargin;
			this.container.style.transition = "all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1)";
			this.container.style.left = `${targetLeftPx}px`;
			this.container.style.top = `${targetTopPx}px`;
			const config = {
				anchorX: isRight ? "right" : "left",
				x: targetMargin,
				anchorY: isBottom ? "bottom" : "top",
				y: isBottom ? window.innerHeight - targetTopPx - rect.height : targetTopPx
			};
			if (typeof GM_setValue !== "undefined") GM_setValue(STORAGE_KEYS.FAB_POSITION, JSON.stringify(config));
			this.snapTimer = setTimeout(() => {
				this.snapTimer = null;
				if (!this.isDragging) this.applyAnchor(config);
			}, 300);
			if (this.onDragEndCallback) this.onDragEndCallback();
		} else this.applyAnchor(this.currentConfig);
	};
	destroy() {
		this.isDragging = false;
		if (this.rafId !== null) {
			cancelAnimationFrame(this.rafId);
			this.rafId = null;
		}
		if (this.snapTimer !== null) {
			clearTimeout(this.snapTimer);
			this.snapTimer = null;
		}
		this.documentCleanup.forEach((fn) => fn());
		this.documentCleanup = [];
		this.trigger.removeEventListener("mousedown", this.onDragStart);
		this.trigger.removeEventListener("touchstart", this.onDragStart);
	}
};
var log$4 = Logger.scope("QuickBar");
var QUICKBAR_TOGGLE_PROPS = [
	"hideViewed",
	"hideNoMagnet",
	"hideCensored",
	"isSelectionMode"
];
var QUICKBAR_SYNC_SUCCESS_DOT_DURATION_MS = TIMING.TOAST_DEFAULT_DURATION;
var isQuickBarToggleProp = (prop) => QUICKBAR_TOGGLE_PROPS.includes(prop);
var getQuickBarSuccessDotRemainingMs = (syncStatus, lastSyncResult, now = Date.now()) => {
	if (syncStatus !== SYNC_STATUS.SUCCESS || !lastSyncResult?.completedAt) return 0;
	const completedAt = Date.parse(lastSyncResult.completedAt);
	if (Number.isNaN(completedAt)) return 0;
	return Math.max(0, QUICKBAR_SYNC_SUCCESS_DOT_DURATION_MS - (now - completedAt));
};
var shouldShowQuickBarSyncDot = (syncMode, syncStatus, lastSyncResult, now = Date.now()) => {
	if (syncMode === "none" || syncStatus === SYNC_STATUS.IDLE) return false;
	if (syncStatus === SYNC_STATUS.SUCCESS) return getQuickBarSuccessDotRemainingMs(syncStatus, lastSyncResult, now) > 0;
	return true;
};
var formatQuickBarFilterTitle = (baseTitle, count) => count > 0 ? `${baseTitle} (${count})` : baseTitle;
var applyQuickBarFilterCountUpdate = (actions, counts) => {
	QUICKBAR_TOGGLE_PROPS.forEach((prop) => {
		if (prop === "isSelectionMode") return;
		const button = actions.querySelector(`[data-prop="${prop}"]`);
		if (!button) return;
		const nextTitle = formatQuickBarFilterTitle(button.getAttribute("data-base-title") || button.getAttribute("data-title") || "", Number(counts[prop] || 0));
		button.setAttribute("data-title", nextTitle);
		button.setAttribute("aria-label", nextTitle);
		button.setAttribute("title", nextTitle);
	});
};
var readQuickBarFilterCountsFromBody = () => ({
	hideViewed: Number(document.body.dataset.fc2HideViewedCount || 0),
	hideNoMagnet: Number(document.body.dataset.fc2HideNoMagnetCount || 0),
	hideCensored: Number(document.body.dataset.fc2HideCensoredCount || 0)
});
var applyQuickBarStateChange = (elements, change, state) => {
	const dot = elements.trigger.querySelector(".fc2-sync-dot");
	if (dot) {
		const nextSyncMode = change.prop === "syncMode" ? String(change.value) : state.syncMode;
		const nextSyncStatus = change.prop === "syncStatus" ? String(change.value) : state.syncStatus;
		const nextSyncResult = change.prop === "lastSyncResult" ? change.value ?? null : state.lastSyncResult;
		if (change.prop === "syncStatus") dot.className = `fc2-sync-dot ${String(change.value)}`;
		if (change.prop === "syncStatus" || change.prop === "syncMode" || change.prop === "lastSyncResult") dot.style.display = shouldShowQuickBarSyncDot(nextSyncMode, nextSyncStatus, nextSyncResult) ? "block" : "none";
	}
	if (change.prop === "syncStatus") elements.syncButton.classList.toggle("active", change.value === "syncing");
	if (isQuickBarToggleProp(change.prop)) {
		const button = elements.actions.querySelector(`[data-prop="${change.prop}"]`);
		if (button) button.classList.toggle("active", !!change.value);
	}
};
var createQuickBarButton = (iconSvg, title, prop, onClick) => {
	const appState = resolveService(STATE_SERVICE).proxy;
	return h("button", {
		className: `fc2-fab-btn ${prop && appState[prop] ? "active" : ""}`,
		"data-title": title,
		"data-base-title": title,
		title,
		...prop ? { "data-prop": prop } : {},
		onclick: (event) => {
			event.preventDefault();
			event.stopPropagation();
			if (prop) {
				appState[prop] = !appState[prop];
				return;
			}
			onClick?.();
		}
	}, UIUtils.icon(iconSvg));
};
var createQuickBarView = () => {
	const container = h("div", { className: "fc2-fab-container" });
	const actions = h("div", { className: "fc2-fab-actions" });
	const syncButton = createQuickBarButton(IconRotate, t("labelSyncing"), null, () => {
		if (resolveService(STATE_SERVICE).proxy.syncMode === "none") {
			Toast.show(t("syncModeNone"), "info");
			SettingsPanel.show("data");
			return;
		}
		if (resolveService(STATE_SERVICE).proxy.syncStatus === "syncing") {
			Toast.show(t("labelSyncing"), "info");
			return;
		}
		resolveService(SYNC_SERVICE).performSync(true).catch(() => void 0);
	});
	actions.append(syncButton, createQuickBarButton(IconEyeSlash, t("optionHideViewed"), "hideViewed"), createQuickBarButton(IconMagnet, t("optionHideNoMagnet"), "hideNoMagnet"), createQuickBarButton(IconBan, t("optionHideCensored"), "hideCensored"), createQuickBarButton(IconListUl, t("labelSelectionMode"), "isSelectionMode"), createQuickBarButton(IconGear, t("tabSettings"), null, () => SettingsPanel.show()), createQuickBarButton(IconArrowUp, t("btnBackToTop"), null, () => window.scrollTo({
		top: 0,
		behavior: "smooth"
	})));
	const trigger = h("button", {
		type: "button",
		className: "fc2-fab-trigger",
		"aria-label": t("btnMoreOptions")
	}, UIUtils.icon(IconPlus), h("div", {
		className: `fc2-sync-dot ${resolveService(STATE_SERVICE).proxy.syncStatus}`,
		style: { display: shouldShowQuickBarSyncDot(resolveService(STATE_SERVICE).proxy.syncMode, resolveService(STATE_SERVICE).proxy.syncStatus, resolveService(STATE_SERVICE).proxy.lastSyncResult) ? "block" : "none" }
	}));
	applyQuickBarFilterCountUpdate(actions, readQuickBarFilterCountsFromBody());
	container.append(actions, trigger);
	return {
		container,
		actions,
		trigger,
		syncButton
	};
};
var QuickBarService = class {
	container = null;
	hostWrapper = null;
	documentCleanup = [];
	serviceCleanup = [];
	syncDotHideTimer = null;
	onInit() {
		log$4.debug("Initializing QuickBar");
		this.cleanupServiceListeners();
		this.render();
		this.bindGlobalEvents();
	}
	onCleanup() {
		this.cleanupDocumentListeners();
		this.cleanupServiceListeners();
		this.unmount();
	}
	bindGlobalEvents() {
		this.serviceCleanup.push(CoreEvents.on(AppEvents.LANGUAGE_CHANGED, () => {
			resolveService(MENU_SERVICE).register();
			this.render();
		}));
		this.serviceCleanup.push(CoreEvents.on(AppEvents.PANEL_OPENED, () => {
			if (this.container) this.container.style.display = "none";
		}));
		this.serviceCleanup.push(CoreEvents.on(AppEvents.PANEL_CLOSED, () => {
			if (this.container) this.container.style.display = "";
		}));
	}
	cleanupDocumentListeners() {
		this.clearSyncDotHideTimer();
		this.documentCleanup.forEach((fn) => fn());
		this.documentCleanup = [];
	}
	clearSyncDotHideTimer() {
		if (this.syncDotHideTimer) {
			clearTimeout(this.syncDotHideTimer);
			this.syncDotHideTimer = null;
		}
	}
	syncDotDisplayState() {
		return shouldShowQuickBarSyncDot(resolveService(STATE_SERVICE).proxy.syncMode, resolveService(STATE_SERVICE).proxy.syncStatus, resolveService(STATE_SERVICE).proxy.lastSyncResult) ? "block" : "none";
	}
	scheduleSyncDotAutoHide(trigger) {
		this.clearSyncDotHideTimer();
		const remainingMs = getQuickBarSuccessDotRemainingMs(resolveService(STATE_SERVICE).proxy.syncStatus, resolveService(STATE_SERVICE).proxy.lastSyncResult);
		if (remainingMs <= 0) return;
		this.syncDotHideTimer = setTimeout(() => {
			const dot = trigger.querySelector(".fc2-sync-dot");
			if (dot) dot.style.display = this.syncDotDisplayState();
			this.syncDotHideTimer = null;
		}, remainingMs);
	}
	bindStateUpdates(trigger, syncButton, actions) {
		const unsubscribe = resolveService(STATE_SERVICE).on((change) => {
			applyQuickBarStateChange({
				trigger,
				syncButton,
				actions
			}, {
				prop: String(change.prop),
				value: change.value
			}, {
				syncMode: resolveService(STATE_SERVICE).proxy.syncMode,
				syncStatus: resolveService(STATE_SERVICE).proxy.syncStatus,
				lastSyncResult: resolveService(STATE_SERVICE).proxy.lastSyncResult
			});
			if (change.prop === "syncStatus" || change.prop === "syncMode" || change.prop === "lastSyncResult") this.scheduleSyncDotAutoHide(trigger);
		});
		this.documentCleanup.push(unsubscribe);
		const filterUnsubscribe = CoreEvents.on(AppEvents.FILTER_COUNTS_CHANGED, (counts) => {
			applyQuickBarFilterCountUpdate(actions, counts);
		});
		this.documentCleanup.push(filterUnsubscribe);
		applyQuickBarFilterCountUpdate(actions, readQuickBarFilterCountsFromBody());
		this.scheduleSyncDotAutoHide(trigger);
	}
	cleanupServiceListeners() {
		this.serviceCleanup.forEach((fn) => fn());
		this.serviceCleanup = [];
	}
	mount(container) {
		this.container = container;
		this.hostWrapper = h("div", { className: "fc2-quickbar-host" }, container);
		UIHost.add(this.hostWrapper);
	}
	unmount() {
		this.container?.remove();
		this.hostWrapper?.remove();
		this.container = null;
		this.hostWrapper = null;
	}
	render() {
		this.cleanupDocumentListeners();
		this.unmount();
		if (!resolveService(STATE_SERVICE).proxy.enableQuickBar) return;
		const { container, actions, trigger, syncButton } = createQuickBarView();
		this.bindStateUpdates(trigger, syncButton, actions);
		this.initDraggable(container, trigger, actions);
		this.mount(container);
	}
	initDraggable(container, trigger, actions) {
		const dragManager = new DragManager(container, trigger);
		trigger.addEventListener("click", (event) => {
			event.preventDefault();
			event.stopPropagation();
			if (!dragManager.hasMoved) {
				const visible = actions.classList.toggle("visible");
				trigger.classList.toggle("active", visible);
			}
		});
		const closeFAB = (event) => {
			if (!event.composedPath().includes(container) && actions.classList.contains("visible")) {
				actions.classList.remove("visible");
				trigger.classList.remove("active");
			}
		};
		document.addEventListener("click", closeFAB, true);
		this.documentCleanup.push(() => {
			document.removeEventListener("click", closeFAB, true);
			dragManager.destroy();
		});
	}
};
var QuickBar = new QuickBarService();
/**
* Explicit module registration — composition root wiring.
* Services are constructed here via create*() + deps, then registered by token.
* UI bridges are wired from app/lifecycle after registration.
*/
var log$3 = Logger.scope("Modules");
var registered = false;
/** Register tokens only — no UI side-effects. */
function registerModules() {
	if (registered) {
		log$3.debug("Modules already registered");
		return;
	}
	registered = true;
	registerAllSites(registerSiteConfig);
	const messaging = createMessagingService();
	const state = createStateService({ messaging });
	setLocaleReader(() => state.get("language"));
	const data = Repository;
	const history = createHistoryService({
		data,
		state,
		messaging
	});
	const cleanup = createCleanupService({ data });
	const scraper = createScraperService();
	const magnet = createMagnetService({
		data,
		scraper
	});
	const preview = createPreviewService();
	const portal = createPortalService();
	const backup = createBackupService({
		data,
		state
	});
	const sync = createSyncService({
		data,
		state
	});
	const menu = createMenuService();
	const site = createSiteService();
	const grid = createGridManager();
	const style = createStyleManager();
	const webdav = createWebDAVProvider({
		data,
		state,
		history
	});
	const supabase = createSupabaseProvider({
		data,
		state,
		history
	});
	const cardEnhancement = createCardEnhancementService();
	configureProviderRegistry({
		state,
		webdav,
		supabase
	});
	AppContainer.registerToken(DATA_SERVICE, data);
	AppContainer.registerToken(STATE_SERVICE, state);
	AppContainer.registerToken(MESSAGING_SERVICE, messaging);
	AppContainer.registerToken(STYLE_SERVICE, style);
	AppContainer.registerToken(GRID_SERVICE, grid);
	AppContainer.registerToken(SITE_SERVICE, site);
	AppContainer.registerToken(HISTORY_SERVICE, history);
	AppContainer.registerToken(TIMELINE_SERVICE, history);
	AppContainer.registerToken(CLEANUP_SERVICE, cleanup);
	AppContainer.registerToken(SCRAPER_SERVICE, scraper);
	AppContainer.registerToken(MAGNET_SERVICE, magnet);
	AppContainer.registerToken(PREVIEW_SERVICE, preview);
	AppContainer.registerToken(PORTAL_SERVICE, portal);
	AppContainer.registerToken(BACKUP_SERVICE, backup);
	AppContainer.registerToken(SYNC_SERVICE, sync);
	AppContainer.registerToken(MENU_SERVICE, menu);
	AppContainer.registerToken(WEBDAV_PROVIDER, webdav);
	AppContainer.registerToken(SUPABASE_PROVIDER, supabase);
	AppContainer.registerToken(CARD_ENHANCEMENT_SERVICE, cardEnhancement);
	AppContainer.registerToken(UI_MANAGER, UIManagerInstance);
	AppContainer.registerToken(SELECTION_SERVICE, SelectionService);
	AppContainer.registerToken(QUICKBAR_SERVICE, QuickBar);
	configureSitePorts({
		fetchActressFromFD2: (id) => scraper.fetchActressFromFD2(id),
		resetFD2Backoff: () => scraper.resetFD2Backoff(),
		addToHistory: (id) => history.add(id).catch((error) => {
			log$3.error("SitePorts addToHistory failed", error);
		}),
		cacheSet: (key, value) => {
			data.cache.set(key, value).catch((error) => log$3.error("SitePorts cacheSet failed", error));
		}
	});
	log$3.info("All modules registered");
}
/**
* Application bootstrap — composition root entry used by main.ts
*/
var log$2 = Logger.scope("Bootstrap");
async function bootstrapApp() {
	registerModules();
	wireUiBridges();
	if (typeof window !== "undefined") window.addEventListener("pagehide", () => revokeAllMediaBlobUrls(), { once: true });
	Logger.group("Main", `${SCRIPT_INFO.NAME} bootstrap`);
	try {
		await AppContainer.bootstrap();
		CoreEvents.emit(AppEvents.UI_READY, {});
		log$2.info("System initialized");
	} finally {
		Logger.groupEnd();
	}
}
var log$1 = Logger.scope("ErrorBoundary");
var ERROR_LOG_KEY = "fc2_error_log";
var MAX_ERROR_ENTRIES = 30;
/**
* Record an error to the persistent error log.
* This provides visibility into background failures that users can't see via Toast.
*/
var recordError = (module, error) => {
	try {
		const entry = {
			timestamp: (/* @__PURE__ */ new Date()).toISOString(),
			module,
			message: error instanceof Error ? error.message : String(error),
			stack: error instanceof Error ? error.stack?.split("\n").slice(0, 5).join("\n") : void 0
		};
		const existing = getErrorLog();
		existing.unshift(entry);
		if (existing.length > MAX_ERROR_ENTRIES) existing.length = MAX_ERROR_ENTRIES;
		Storage.set(ERROR_LOG_KEY, existing);
	} catch {}
};
/**
* Get the persistent error log (most recent first).
*/
var getErrorLog = () => {
	try {
		return Storage.get(ERROR_LOG_KEY, []) || [];
	} catch {
		return [];
	}
};
/**
* Install global error handlers to capture unhandled errors.
* Call this once during app initialization.
*/
var installGlobalErrorHandlers = () => {
	window.addEventListener("unhandledrejection", (event) => {
		recordError("unhandled-promise", event.reason);
		log$1.error("Unhandled promise rejection", event.reason);
	});
	window.addEventListener("error", (event) => {
		recordError("uncaught-error", event.error || event.message);
		log$1.error("Uncaught error", event.error || event.message);
	});
};
var log = Logger.scope("Main");
installGlobalErrorHandlers();
var isCloudflareChallenge = () => document.title.includes("Just a moment...") || !!document.querySelector("#challenge-running") || !!document.querySelector("iframe[src*=\"challenges.cloudflare.com\"]");
var main = async () => {
	Logger.init();
	if (isCloudflareChallenge()) {
		log.warn("Cloudflare challenge detected, skipping initialization");
		return;
	}
	try {
		await bootstrapApp();
	} catch (error) {
		log.error("Bootstrap failed", error);
	}
};
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", () => {
	main();
});
else main();})}}));
System.register("./DataTab-bfpDUmRQ-C_bNm5Gc.js", ['./___monkey.entry.js','./components-1yyvCgmd-C1dNzRD5.js','./TabLifecycle-BwHTz_iJ-BnPxZNF9.js','dexie'],(function(exports){'use strict';var consumePendingSettingsNavigationIntent,resolveService,STATE_SERVICE,h,t,CoreEvents,AppEvents,UIUtils,IconFileImport,IconFileExport,IconSliders,IconPlay,DATA_SERVICE,Logger,Toast,HISTORY_SERVICE,SYNC_SERVICE,IconLink,BACKUP_SERVICE,formatBackupImportDiff,TIMING,STORAGE_KEYS,SCRIPT_INFO,IconDatabase,IconServer,IconRotate,IconVial,SCRAPER_SERVICE,getSyncCursorStorageKey,Card,StatItem,Button,FormRow,Input,PasswordInput,Select,TabLifecycle;return{setters:[function(module){consumePendingSettingsNavigationIntent=module.c;resolveService=module.r;STATE_SERVICE=module.S;h=module.h;t=module.t;CoreEvents=module.C;AppEvents=module.A;UIUtils=module.U;IconFileImport=module.I;IconFileExport=module.a;IconSliders=module.b;IconPlay=module.d;DATA_SERVICE=module.D;Logger=module.L;Toast=module.T;HISTORY_SERVICE=module.H;SYNC_SERVICE=module.e;IconLink=module.f;BACKUP_SERVICE=module.B;formatBackupImportDiff=module.g;TIMING=module.i;STORAGE_KEYS=module.j;SCRIPT_INFO=module.k;IconDatabase=module.l;IconServer=module.m;IconRotate=module.n;IconVial=module.o;SCRAPER_SERVICE=module.p;getSyncCursorStorageKey=module.q;},function(module){Card=module.C;StatItem=module.S;Button=module.B;FormRow=module.F;Input=module.I;PasswordInput=module.P;Select=module.a;},function(module){TabLifecycle=module.T;},null],execute:(function(){var capitalize = (value) => value ? `${value.charAt(0).toUpperCase()}${value.slice(1)}` : value;
var formatDataTabLastSync = (rawValue, neverLabel, formatter = (value) => new Date(value).toLocaleString()) => {
	if (rawValue === null || rawValue === void 0 || rawValue === "" || rawValue === neverLabel) return neverLabel;
	if (typeof rawValue === "number") return Number.isFinite(rawValue) ? formatter(rawValue) : neverLabel;
	const parsedTime = Date.parse(rawValue);
	if (Number.isNaN(parsedTime)) return rawValue;
	return formatter(rawValue);
};
var buildDataTabSyncViewModel = (syncMode, syncStatus, translate, lastSyncDisplay, syncResult) => {
	const modeLabel = syncMode === "webdav" ? translate("syncModeWebDAV") : syncMode === "supabase" ? translate("syncModeSupabase") : translate("syncModeNone");
	const resultSummary = syncResult?.summary ? ` · ${syncResult.summary}` : "";
	if (syncMode === "none") return {
		modeLabel,
		statusLabel: modeLabel,
		detail: `${translate("labelLastSync")}: ${lastSyncDisplay}${resultSummary}`,
		tone: "idle"
	};
	return {
		modeLabel,
		statusLabel: capitalize(syncStatus),
		detail: `${translate("labelLastSync")}: ${lastSyncDisplay}${resultSummary}`,
		tone: syncStatus
	};
};
var buildSyncResultPanelViewModel = (syncResult, neverLabel, translate, formatter = (value) => new Date(value).toLocaleString()) => {
	if (!syncResult) return {
		title: translate("syncLatestTitle"),
		summary: translate("syncLatestEmpty"),
		completedAt: neverLabel,
		conflictText: "",
		conflictStats: []
	};
	const conflictStats = syncResult.conflict ? [
		translate("syncConflictLocalNewer", { count: syncResult.conflict.localNewer }),
		translate("syncConflictRemoteNewer", { count: syncResult.conflict.remoteNewer }),
		translate("syncConflictMerged", { count: syncResult.conflict.merged })
	] : [];
	const detailText = syncResult.errorMessage || syncResult.conflict?.explanation || "";
	return {
		title: translate("syncLatestTitle"),
		summary: syncResult.detailSummary || syncResult.summary,
		completedAt: syncResult.completedAt ? formatDataTabLastSync(syncResult.completedAt, neverLabel, formatter) : neverLabel,
		conflictText: detailText,
		conflictStats
	};
};
var buildBackupDiffLines = (diff, translate) => {
	const lines = [
		translate("backupSettingsAdded", { count: diff.settingsAdded.length }),
		translate("backupSettingsChanged", { count: diff.settingsChanged.length }),
		translate("backupHistoryRows", { count: diff.historyToImport })
	];
	if (diff.settingsChanged.length > 0) lines.push(translate("backupChangedKeys", { keys: diff.settingsChanged.join(", ") }));
	return lines;
};
/**
* Data tab storage section — backup, cache/history reset, debug env.
*/
var BACKUP_IMPORT_ERROR_TRANSLATION_KEYS = {
	invalid_json: "backupImportErrorInvalidJson",
	invalid_payload: "backupImportErrorInvalidPayload",
	unsupported_version: "backupImportErrorUnsupportedVersion",
	read_failed: "backupImportErrorReadFailed",
	apply_failed: "backupImportErrorApplyFailed"
};
var buildStoragePreviewEntries = (textValue, tags = []) => {
	const lines = textValue.split(/\r?\n/).filter(Boolean);
	return [...tags, ...lines];
};
var shouldConfirmDestructiveAction = (confirmDialog, confirmResetMessage, confirmDestructiveMessage) => confirmDialog(confirmResetMessage) && confirmDialog(confirmDestructiveMessage);
var buildDebugEnvPayload = (input) => JSON.stringify({
	version: input.version,
	ua: input.ua,
	url: input.url,
	syncMode: input.syncMode,
	debugMode: input.debugMode
}, null, 2);
var getBackupImportErrorTranslationKey = (errorCode) => BACKUP_IMPORT_ERROR_TRANSLATION_KEYS[errorCode];
var buildBackupImportErrorMessage = (errorCode, translate) => translate(getBackupImportErrorTranslationKey(errorCode));
var createBackupFilePicker = () => new Promise((resolve) => {
	const input = h("input", {
		type: "file",
		accept: ".json"
	});
	input.onchange = () => resolve(input.files?.[0] || null);
	input.click();
});
var showImportError = (message) => {
	Toast.show(message || t("alertImportError"), "error");
};
var createDestructiveHandler = (action, successMessage, render, confirmDialog) => async () => {
	if (!shouldConfirmDestructiveAction(confirmDialog, t("confirmResetDatabase"), t("confirmDestructiveAction"))) return;
	await action();
	Toast.show(successMessage, "success");
	render("data");
};
var createImportHandler = ({ setPreview, pickBackupFile, confirmDialog, reloadPage }) => async () => {
	const file = await pickBackupFile();
	if (!file) return;
	const previewResult = await resolveService(BACKUP_SERVICE).previewImportDetailed(file);
	if (!previewResult.ok || !previewResult.preview) {
		const errorMessage = buildBackupImportErrorMessage(previewResult.errorCode ?? "invalid_payload", t);
		setPreview(errorMessage, [t("labelError")]);
		Toast.show(errorMessage, "error");
		return;
	}
	const preview = previewResult.preview;
	const tags = buildBackupDiffLines(preview.diff, t);
	const previewText = formatBackupImportDiff(preview.diff, t);
	setPreview(previewText, tags);
	if (!confirmDialog(`${t("backupImportConfirm")}

${previewText}`)) return;
	const importResult = await resolveService(BACKUP_SERVICE).importDataDetailed(file);
	if (!importResult.success) {
		const errorMessage = buildBackupImportErrorMessage(importResult.errorCode ?? "apply_failed", t);
		setPreview(errorMessage, [t("labelError")]);
		showImportError(errorMessage);
		return;
	}
	Toast.show(t("alertImportSuccess"), "success");
	setTimeout(() => reloadPage(), TIMING.RELOAD_DELAY_NORMAL);
};
var createExportHandler = () => async () => {
	if (!await resolveService(BACKUP_SERVICE).exportData()) {
		showImportError();
		return;
	}
	Toast.show(t("backupExportSuccessPlain"), "success");
};
var createStorageSectionActions = ({ render, setPreview, pickBackupFile, confirmDialog, reloadPage, copyText }) => ({
	handleExport: createExportHandler(),
	handleImport: createImportHandler({
		setPreview,
		pickBackupFile,
		confirmDialog,
		reloadPage
	}),
	handleClearCache: createDestructiveHandler(() => resolveService(DATA_SERVICE).cache.clear(), t("alertCacheCleared"), render, confirmDialog),
	handleClearHistory: createDestructiveHandler(() => resolveService(HISTORY_SERVICE).clear(), t("alertHistoryCleared"), render, confirmDialog),
	handleToggleDebugMode: () => {
		resolveService(STATE_SERVICE).proxy.debugMode = !resolveService(STATE_SERVICE).proxy.debugMode;
		Toast.show(resolveService(STATE_SERVICE).proxy.debugMode ? t("alertDebugOn") : t("alertDebugOff"), "info");
		render("data");
	},
	handleCopyEnv: () => {
		copyText(buildDebugEnvPayload({
			version: SCRIPT_INFO.VERSION,
			ua: navigator.userAgent,
			url: location.href,
			syncMode: resolveService(STATE_SERVICE).proxy.syncMode,
			debugMode: resolveService(STATE_SERVICE).proxy.debugMode
		}));
		Toast.show(t("alertLogsCopied"), "success");
	}
});
var buildStorageSection = (render) => {
	const previewLines = h("div", { className: "fc2-backup-preview-lines" });
	const setPreview = (textValue, tags = []) => {
		previewLines.textContent = "";
		const entries = buildStoragePreviewEntries(textValue, tags);
		if (entries.length === 0) {
			previewLines.classList.add("fc2-is-hidden");
			return;
		}
		previewLines.classList.remove("fc2-is-hidden");
		entries.forEach((entry) => {
			previewLines.appendChild(h("div", { className: "fc2-backup-preview-line" }, entry));
		});
	};
	const actions = createStorageSectionActions({
		render,
		setPreview,
		pickBackupFile: createBackupFilePicker,
		confirmDialog: (message) => confirm(message),
		reloadPage: () => location.reload(),
		copyText: (text) => {
			navigator.clipboard.writeText(text);
		}
	});
	setPreview("");
	return Card({
		title: t("groupDataManagement"),
		icon: IconDatabase,
		children: [
			h("div", { className: "fc2-grid-actions" }, Button({
				text: t("btnClearCache"),
				className: "danger ghost",
				onClick: actions.handleClearCache
			}), Button({
				text: t("btnClearHistory"),
				className: "danger ghost",
				onClick: actions.handleClearHistory
			}), Button({
				text: t("btnExportData"),
				icon: UIUtils.icon(IconFileExport),
				onClick: actions.handleExport
			}), Button({
				text: t("btnImportData"),
				icon: UIUtils.icon(IconFileImport),
				onClick: actions.handleImport
			})),
			h("div", { className: "fc2-sync-result-panel" }, previewLines),
			FormRow({
				label: t("labelDebugMode"),
				children: h("div", { className: "fc2-input-group" }, Button({
					text: resolveService(STATE_SERVICE).proxy.debugMode ? t("statusDebugOn") : t("statusDebugOff"),
					className: resolveService(STATE_SERVICE).proxy.debugMode ? "primary" : "",
					onClick: actions.handleToggleDebugMode
				}), Button({
					text: t("btnCopyEnv"),
					icon: UIUtils.icon(IconSliders),
					onClick: actions.handleCopyEnv
				}))
			})
		]
	});
};
var toErrorMessage = (error) => {
	let message = error instanceof Error ? error.message : String(error);
	if (error && typeof error === "object" && "response" in error && typeof error.response === "string") try {
		const parsed = JSON.parse(error.response);
		if (parsed.message) message = `${parsed.message}${parsed.hint ? ` (${parsed.hint})` : ""}`;
	} catch {
		return message;
	}
	return message;
};
var getSyncProviderDiagnostic = (mode, syncResult) => {
	if (!syncResult || syncResult.mode !== mode) return "";
	if (syncResult.status === "error") return syncResult.errorMessage || syncResult.summary;
	if (syncResult.status === "conflict") return syncResult.conflict?.explanation || syncResult.summary;
	return "";
};
var confirmDestructiveSyncAction = (mode) => {
	return confirm(t(mode === "push" ? "alertPushAllQuery" : "alertPullAllQuery"));
};
var runDataTabSyncAction = async (action) => {
	try {
		await action();
	} catch (error) {
		Toast.show(`${t("labelError") || "Error"}: ${toErrorMessage(error)}`, "error", { duration: 1e4 });
	}
};
var createSyncStatusSummary = (provider, lifecycle, getLastSyncDisplay) => {
	const lastSyncText = h("p", { className: "fc2-dim" }, `${t("labelLastSync")}: ${getLastSyncDisplay()}`);
	const diagnosticText = h("p", { className: "fc2-dim" });
	const refreshStatus = () => {
		lastSyncText.textContent = `${t("labelLastSync")}: ${getLastSyncDisplay()}`;
		diagnosticText.textContent = getSyncProviderDiagnostic(provider, resolveService(STATE_SERVICE).proxy.lastSyncResult);
		diagnosticText.classList.toggle("fc2-is-hidden", !diagnosticText.textContent);
	};
	lifecycle.addCleanup(resolveService(STATE_SERVICE).on("syncStatus", refreshStatus));
	lifecycle.addCleanup(resolveService(STATE_SERVICE).on("lastSyncResult", refreshStatus));
	refreshStatus();
	return {
		lastSyncText,
		diagnosticText,
		refreshStatus
	};
};
/**
* Data tab view builders — storage, sync config, WebDAV, Supabase.
*/
var createProviderActionRow = (actions) => h("div", { className: "fc2-card-actions" }, ...actions);
var createSyncStatusSection = ({ lastSyncText, diagnosticText, actions }) => h("div", { className: "fc2-auth-section" }, lastSyncText, diagnosticText, createProviderActionRow(actions));
var createStandardSyncButtons = ({ syncLabel, forcePushLabel, forcePullLabel, onSync, onForcePush, onForcePull, confirmDestructive }) => {
	const buttons = [Button({
		text: syncLabel,
		className: "primary",
		onClick: onSync
	}), Button({
		text: forcePushLabel,
		className: "danger ghost",
		onClick: () => {
			if (!confirmDestructive("push")) return;
			onForcePush();
		}
	})];
	if (forcePullLabel && onForcePull) buttons.push(Button({
		text: forcePullLabel,
		className: "danger ghost",
		onClick: () => {
			if (!confirmDestructive("pull")) return;
			onForcePull();
		}
	}));
	return buttons;
};
var createSyncStatusElements = (render) => {
	const statusPill = h("span", { className: "fc2-sync-status-pill idle" }, t("syncModeNone"));
	const statusTitle = h("span", { className: "fc2-sync-summary-title" }, t("syncModeNone"));
	const statusDetail = h("span", { className: "fc2-sync-summary-detail" }, `${t("labelLastSync")}: ${t("labelNever")}`);
	const resultSummary = h("div", { className: "fc2-sync-result-summary" }, t("syncLatestEmpty"));
	const resultMeta = h("div", { className: "fc2-label-dim" }, `${t("labelLastSync")}: ${t("labelNever")}`);
	const conflictHint = h("div", { className: "fc2-label-dim" }, t("syncNoConflictYet"));
	const conflictStats = h("div", { className: "fc2-sync-conflicts-list" });
	return {
		statusPill,
		statusTitle,
		statusDetail,
		resultSummary,
		resultMeta,
		conflictHint,
		conflictStats,
		element: h("div", { className: "fc2-sync-result-panel" }, h("div", { className: "fc2-sync-summary" }, statusPill, h("div", { className: "fc2-sync-summary-copy" }, statusTitle, statusDetail)), h("div", { className: "fc2-sync-result-body" }, resultSummary, resultMeta, conflictHint, conflictStats), h("div", { className: "fc2-portal-actions" }, Button({
			text: t("syncActionDebugLogs"),
			className: "ghost",
			onClick: () => render("debug")
		})))
	};
};
var createSummaryRefresher = (ui, getLastSyncDisplay) => {
	return () => {
		const viewModel = buildDataTabSyncViewModel(resolveService(STATE_SERVICE).proxy.syncMode, resolveService(STATE_SERVICE).proxy.syncStatus, t, getLastSyncDisplay(), resolveService(STATE_SERVICE).proxy.lastSyncResult);
		const resultView = buildSyncResultPanelViewModel(resolveService(STATE_SERVICE).proxy.lastSyncResult, t("labelNever"), t);
		ui.statusPill.className = `fc2-sync-status-pill ${viewModel.tone}`;
		ui.statusPill.textContent = viewModel.statusLabel;
		ui.statusTitle.textContent = viewModel.modeLabel;
		ui.statusDetail.textContent = viewModel.detail;
		ui.resultSummary.textContent = resultView.summary;
		ui.resultMeta.textContent = `${t("syncCompletedAt")}: ${resultView.completedAt}`;
		ui.conflictHint.textContent = resultView.conflictText || t("syncNoConflictYet");
		ui.conflictStats.textContent = "";
		resultView.conflictStats.forEach((line) => {
			ui.conflictStats.appendChild(h("div", { className: "fc2-label-dim" }, line));
		});
	};
};
var buildSyncModeRow = (ctrl, render) => FormRow({
	label: t("labelSyncMode"),
	children: Select({
		id: "syncMode",
		options: [
			{
				value: "none",
				label: t("syncModeNone")
			},
			{
				value: "webdav",
				label: t("syncModeWebDAV")
			},
			{
				value: "supabase",
				label: t("syncModeSupabase")
			}
		],
		controller: ctrl,
		binding: "syncMode",
		onChange: () => void render("data")
	})
});
var buildSyncIntervalRow = (ctrl) => FormRow({
	label: t("labelSyncInterval"),
	children: Select({
		id: "syncInterval",
		options: [
			{
				value: "0",
				label: t("syncInterval0")
			},
			{
				value: "2",
				label: t("syncInterval2")
			},
			{
				value: "5",
				label: t("syncInterval5")
			},
			{
				value: "10",
				label: t("syncInterval10")
			},
			{
				value: "30",
				label: t("syncInterval30")
			},
			{
				value: "-1",
				label: t("syncIntervalManual")
			}
		],
		controller: ctrl,
		binding: "syncInterval"
	})
});
var buildSyncSettingsRows = (ctrl, render) => {
	const rows = [buildSyncModeRow(ctrl, render)];
	if (resolveService(STATE_SERVICE).proxy.syncMode !== "none") rows.push(buildSyncIntervalRow(ctrl));
	return rows;
};
var buildSyncConfigSection = (ctrl, render, lifecycle, getLastSyncDisplay) => {
	const ui = createSyncStatusElements(render);
	const refresh = createSummaryRefresher(ui, getLastSyncDisplay);
	lifecycle.addCleanup(resolveService(STATE_SERVICE).on("syncStatus", refresh));
	lifecycle.addCleanup(resolveService(STATE_SERVICE).on("syncMode", refresh));
	lifecycle.addCleanup(resolveService(STATE_SERVICE).on("lastSyncResult", refresh));
	const children = [ui.element, ...buildSyncSettingsRows(ctrl, render)];
	refresh();
	return Card({
		title: t("syncStatus"),
		icon: IconLink,
		children
	});
};
var log = Logger.scope("DataTabWebDAV");
var buildWebDAVSection = (ctrl, lifecycle, getLastSyncDisplay) => {
	const { lastSyncText, diagnosticText } = createSyncStatusSummary("webdav", lifecycle, getLastSyncDisplay);
	return Card({
		title: t("groupWebDAV"),
		icon: IconServer,
		children: [
			FormRow({
				label: t("labelWebDAVUrl"),
				children: Input({
					id: "webdavUrl",
					type: "url",
					controller: ctrl,
					binding: "webdavUrl",
					placeholder: "https://..."
				})
			}),
			FormRow({
				label: t("labelWebDAVUser"),
				children: Input({
					id: "webdavUser",
					type: "text",
					controller: ctrl,
					binding: "webdavUser"
				})
			}),
			FormRow({
				label: t("labelWebDAVPass"),
				children: PasswordInput({
					id: "webdavPass",
					controller: ctrl,
					binding: "webdavPass"
				})
			}),
			createSyncStatusSection({
				lastSyncText,
				diagnosticText,
				actions: [Button({
					text: t("btnWebDAVTest"),
					onClick: async () => {
						try {
							await resolveService(SYNC_SERVICE).testWebDAV();
							Toast.show(t("alertWebDAVSuccess"), "success");
						} catch (e) {
							log.error("WebDAV test failed", e);
							Toast.show(t("alertWebDAVError"), "error");
						}
					}
				}), ...createStandardSyncButtons({
					syncLabel: t("btnWebDAVSync"),
					forcePushLabel: t("btnForceSync"),
					onSync: () => void runDataTabSyncAction(() => resolveService(SYNC_SERVICE).performSync(true)),
					onForcePush: () => void runDataTabSyncAction(() => resolveService(SYNC_SERVICE).forceFullSync(true)),
					confirmDestructive: confirmDestructiveSyncAction
				})]
			})
		]
	});
};
var handleSupabaseConnectAndSync = async (render) => {
	Logger.debug("DataTab", "Connect and Sync clicked", {
		url: resolveService(STATE_SERVICE).proxy.supabaseUrl,
		key: !!resolveService(STATE_SERVICE).proxy.supabaseKey,
		hasEmail: !!resolveService(STATE_SERVICE).proxy.supabaseEmail,
		hasPass: !!resolveService(STATE_SERVICE).proxy.supabasePassword
	});
	try {
		if (!resolveService(STATE_SERVICE).proxy.supabaseUrl || !resolveService(STATE_SERVICE).proxy.supabaseKey) throw new Error(t("alertSbUrlRequired") || "Missing Supabase URL or Key");
		if (resolveService(STATE_SERVICE).proxy.supabaseEmail && resolveService(STATE_SERVICE).proxy.supabasePassword) {
			Logger.debug("DataTab", "Attempting login...");
			await resolveService(SYNC_SERVICE).login(resolveService(STATE_SERVICE).proxy.supabaseEmail, resolveService(STATE_SERVICE).proxy.supabasePassword);
			Toast.show(t("alertSyncAccountConnected") || "Account connected", "success");
			render("data");
		}
		await resolveService(SYNC_SERVICE).performSync(true);
	} catch (error) {
		Logger.error("DataTab", "Action failed", error);
		Toast.show(`${t("labelError") || "Error"}: ${toErrorMessage(error)}`, "error", { duration: 1e4 });
	}
};
var isLoggedIn = () => {
	return !!GM_getValue(STORAGE_KEYS.SYNC_USER_ID);
};
var createSupabaseActionButtons = (render) => {
	if (isLoggedIn()) return [...createStandardSyncButtons({
		syncLabel: t("btnSyncNow"),
		forcePushLabel: t("btnForceSync") || "Force Push",
		forcePullLabel: t("btnPullSync") || "Force Pull",
		onSync: () => void runDataTabSyncAction(() => resolveService(SYNC_SERVICE).performSync(true)),
		onForcePush: () => void runDataTabSyncAction(() => resolveService(SYNC_SERVICE).forceFullSync(true)),
		onForcePull: () => void runDataTabSyncAction(() => resolveService(SYNC_SERVICE).forcePullSync(true)),
		confirmDestructive: confirmDestructiveSyncAction
	}), Button({
		text: t("btnLogout"),
		className: "danger",
		onClick: async () => {
			await resolveService(SYNC_SERVICE).logout();
			render("data");
		}
	})];
	return [Button({
		text: t("btnConnectAndSync"),
		className: "primary",
		onClick: () => handleSupabaseConnectAndSync(render)
	})];
};
var buildSupabaseSection = (ctrl, render, lifecycle, getLastSyncDisplay) => {
	const { lastSyncText, diagnosticText } = createSyncStatusSummary("supabase", lifecycle, getLastSyncDisplay);
	return Card({
		title: t("labelSupabaseSync"),
		icon: IconRotate,
		children: [
			FormRow({
				label: t("labelSupabaseUrl"),
				children: Input({
					id: "supabaseUrl",
					type: "url",
					controller: ctrl,
					binding: "supabaseUrl"
				})
			}),
			FormRow({
				label: t("labelSupabaseKey"),
				children: PasswordInput({
					id: "supabaseKey",
					controller: ctrl,
					binding: "supabaseKey"
				})
			}),
			FormRow({
				label: t("labelAuthEmail") || "Email",
				children: Input({
					id: "supabaseEmail",
					type: "email",
					controller: ctrl,
					binding: "supabaseEmail"
				})
			}),
			FormRow({
				label: t("labelAuthPass"),
				children: PasswordInput({
					id: "supabasePassword",
					controller: ctrl,
					binding: "supabasePassword"
				})
			}),
			createSyncStatusSection({
				lastSyncText,
				diagnosticText,
				actions: createSupabaseActionButtons(render)
			})
		]
	});
};
var lifecycle = new TabLifecycle();
var getLastSyncDisplay = () => {
	return formatDataTabLastSync(typeof GM_getValue !== "undefined" ? GM_getValue(getSyncCursorStorageKey(), t("labelNever")) : t("labelNever"), t("labelNever"), (value) => new Date(value).toLocaleString());
};
var buildDataOverviewSection = (lc) => {
	const cacheValue = h("span", {}, t("labelLoading"));
	const historyValue = h("span", {}, t("labelLoading"));
	let disposed = false;
	const loadCounts = async () => {
		try {
			const [cacheCount, historyCount] = await Promise.all([resolveService(DATA_SERVICE).db.cache.count(), resolveService(DATA_SERVICE).db.history.where("is_deleted").equals(0).count()]);
			if (disposed) return;
			cacheValue.textContent = String(cacheCount);
			historyValue.textContent = String(historyCount);
		} catch (error) {
			Logger.error("DataTab", "Failed to load overview counts", error);
			if (disposed) return;
			cacheValue.textContent = "--";
			historyValue.textContent = "--";
		}
	};
	lc.addCleanup(() => {
		disposed = true;
	});
	lc.addCleanup(CoreEvents.on(AppEvents.COLLECTION_UPDATED, () => void loadCounts()));
	lc.addCleanup(CoreEvents.on(AppEvents.HISTORY_CHANGED, () => void loadCounts()));
	loadCounts();
	return Card({
		title: t("tabStatistics"),
		icon: IconServer,
		children: [h("div", { className: "fc2-settings-card-grid fc2-data-overview-grid" }, StatItem({
			label: t("labelCacheManagement"),
			value: cacheValue
		}), StatItem({
			label: t("labelHistoryManagement"),
			value: historyValue
		}))]
	});
};
var buildSystemDiagnosisSection = () => {
	const statusMap = {};
	const targets = [
		{
			id: "sukebei",
			label: t("scraperTargetSukebei"),
			url: "https://sukebei.nyaa.si"
		},
		{
			id: "fd2",
			label: t("scraperTargetFd2"),
			url: "https://fd2ppv.cc"
		},
		{
			id: "paipancon",
			label: t("scraperTargetPaipancon"),
			url: "https://paipancon.com"
		}
	];
	const createStatusRow = (label, id, url) => {
		const statusEl = h("span", { className: "fc2-status-indicator pending" }, t("scraperStatusPending") || "待测试");
		statusMap[id] = statusEl;
		return h("div", { className: "fc2-scraper-row" }, h("div", { className: "fc2-scraper-info" }, h("span", { className: "fc2-scraper-label" }, label), h("a", {
			href: url,
			target: "_blank",
			rel: "noopener noreferrer",
			className: "fc2-scraper-url micro-link"
		}, UIUtils.icon(IconLink))), statusEl);
	};
	const runTest = async () => {
		for (const target of targets) {
			const el = statusMap[target.id];
			if (!el) continue;
			el.className = "fc2-status-indicator loading";
			el.textContent = t("scraperStatusLoading") || "测试中...";
			const result = await resolveService(SCRAPER_SERVICE).testHealth(target.id);
			el.className = `fc2-status-indicator ${result}`;
			if (result === "ok") el.textContent = t("scraperStatusOk") || "连接成功";
			else if (result === "cf") el.textContent = t("scraperStatusCF") || "Cloudflare 拦截";
			else if (result === "429") el.textContent = t("scraperStatus429") || "请求过于频繁";
			else el.textContent = t("scraperStatusError") || "连接失败";
		}
	};
	return Card({
		title: t("groupScraperDiagnosis"),
		icon: IconVial,
		children: [h("div", { className: "fc2-scraper-list" }, ...targets.map((target) => createStatusRow(target.label, target.id, target.url))), h("div", { className: "fc2-card-actions" }, Button({
			text: t("btnTestScrapers") || "开始连通性诊断",
			icon: UIUtils.icon(IconPlay),
			onClick: runTest
		}))]
	});
};
var renderDataTab = exports("renderDataTab",(_shadow, render) => {
	const ctrl = lifecycle.reset();
	const navigationIntent = consumePendingSettingsNavigationIntent("data");
	const prioritizeSync = navigationIntent?.presetKey === "sync-center";
	const overviewSection = buildDataOverviewSection(lifecycle);
	const storageSection = buildStorageSection(render);
	const syncConfigSection = buildSyncConfigSection(ctrl, render, lifecycle, getLastSyncDisplay);
	const providerSections = [resolveService(STATE_SERVICE).proxy.syncMode === "webdav" ? buildWebDAVSection(ctrl, lifecycle, getLastSyncDisplay) : null, resolveService(STATE_SERVICE).proxy.syncMode === "supabase" ? buildSupabaseSection(ctrl, render, lifecycle, getLastSyncDisplay) : null];
	const sections = prioritizeSync ? [
		syncConfigSection,
		overviewSection,
		storageSection,
		buildSystemDiagnosisSection(),
		...providerSections
	] : [
		overviewSection,
		storageSection,
		buildSystemDiagnosisSection(),
		syncConfigSection,
		...providerSections
	];
	return h("div", {
		className: "fc2-data-container",
		"data-intent-focus": navigationIntent?.presetKey || ""
	}, ...sections.filter((section) => section !== null));
});
var onUnmount = exports("onUnmount",() => {
	lifecycle.dispose();
});})}}));
System.register("./SettingsTab-8ebeRFDG-BDxX9kp7.js", ['./___monkey.entry.js','./components-1yyvCgmd-C1dNzRD5.js','./TabLifecycle-BwHTz_iJ-BnPxZNF9.js','dexie'],(function(exports){'use strict';var consumePendingSettingsNavigationIntent,resolveSettingsFocusGroupsFromIntent,resolveService,PORTAL_SERVICE,h,STATE_SERVICE,t,IconPalette,IconFilter,IconClockRotateLeft,CoreEvents,AppEvents,IconGear,Card,Button,CheckboxRow,FormRow,ColorPicker,Select,TabLifecycle;return{setters:[function(module){consumePendingSettingsNavigationIntent=module.c;resolveSettingsFocusGroupsFromIntent=module.s;resolveService=module.r;PORTAL_SERVICE=module.P;h=module.h;STATE_SERVICE=module.S;t=module.t;IconPalette=module.u;IconFilter=module.v;IconClockRotateLeft=module.w;CoreEvents=module.C;AppEvents=module.A;IconGear=module.x;},function(module){Card=module.C;Button=module.B;CheckboxRow=module.b;FormRow=module.F;ColorPicker=module.c;Select=module.a;},function(module){TabLifecycle=module.T;},null],execute:(function(){var SETTINGS_GROUP_KEYS = [
	"appearance",
	"filters",
	"enhancements",
	"card-actions",
	"portals"
];
var getCollapsedSettingsGroups = (collapsedGroups) => SETTINGS_GROUP_KEYS.filter((groupKey) => collapsedGroups.includes(groupKey));
var applySettingsCollapsedGroups = (grid, collapsedGroups) => {
	const collapsedSet = new Set(getCollapsedSettingsGroups(collapsedGroups));
	grid.querySelectorAll("[data-settings-group]").forEach((card) => {
		const groupKey = card.getAttribute("data-settings-group");
		if (!groupKey) return;
		const isCollapsed = collapsedSet.has(groupKey);
		card.classList.toggle("is-collapsed", isCollapsed);
		const toggle = card.querySelector(".fc2-card-collapse-toggle");
		toggle?.classList.toggle("is-collapsed", isCollapsed);
		toggle?.setAttribute("aria-expanded", String(!isCollapsed));
	});
};
var applySettingsGroupSpotlight = (grid, groupKeys) => {
	const spotlightGroups = new Set(groupKeys);
	grid.querySelectorAll("[data-settings-group]").forEach((card) => {
		const groupKey = card.getAttribute("data-settings-group");
		card.classList.toggle("is-home-spotlight", !!groupKey && spotlightGroups.has(groupKey));
	});
};
var getAllPortalIds = (portals) => portals.map((portal) => portal.id);
var syncPortalGridState = (portalGrid, enabled) => {
	portalGrid.querySelectorAll(".fc2-portal-item").forEach((element) => {
		const portalId = element.getAttribute("data-portal-id");
		if (!portalId) return;
		const isActive = enabled.includes(portalId);
		element.classList.toggle("active", isActive);
		const input = element.querySelector("input");
		if (input instanceof HTMLInputElement) input.checked = isActive;
	});
};
var previewModeOptions = [{
	value: "static",
	label: t("previewModeStatic")
}, {
	value: "hover",
	label: t("previewModeHover")
}];
var cardDensityOptions = [
	{
		value: "cover-only",
		label: t("cardDensityCoverOnly")
	},
	{
		value: "compact",
		label: t("cardDensityCompact")
	},
	{
		value: "standard",
		label: t("cardDensityStandard")
	},
	{
		value: "immersive",
		label: t("cardDensityImmersive")
	}
];
var accentColorOptions = [
	{
		value: "#ffffff",
		label: t("accentColorDefault")
	},
	{
		value: "#cba6f7",
		label: t("accentColorPurple")
	},
	{
		value: "#f5c2e7",
		label: t("accentColorPink")
	},
	{
		value: "#89b4fa",
		label: t("accentColorBlue")
	},
	{
		value: "#a6e3a1",
		label: t("accentColorGreen")
	},
	{
		value: "#fab387",
		label: t("accentColorOrange")
	},
	{
		value: "#f9e2af",
		label: t("accentColorGold")
	}
];
var languageOptions = [
	{
		value: "auto",
		label: t("langAuto")
	},
	{
		value: "zh-CN",
		label: t("langZh")
	},
	{
		value: "en-US",
		label: t("langEn")
	},
	{
		value: "ja-JP",
		label: t("langJa")
	}
];
var gridColumnOptions = [
	0,
	1,
	2,
	3,
	4,
	5,
	6
].map((value) => ({
	value: String(value),
	label: value === 0 ? t("labelDefault") : String(value)
}));
var DEFAULT_CARD_PRIMARY_ACTIONS = [
	"id",
	"viewed",
	"magnet",
	"external"
];
var getCardPrimaryActions = () => {
	const value = resolveService(STATE_SERVICE).proxy.cardPrimaryActions;
	return Array.isArray(value) ? value : DEFAULT_CARD_PRIMARY_ACTIONS;
};
var toggleCardPrimaryAction = (action, checked) => {
	const current = getCardPrimaryActions();
	const next = checked ? Array.from(/* @__PURE__ */ new Set([...current, action])) : current.filter((item) => item !== action);
	resolveService(STATE_SERVICE).proxy.cardPrimaryActions = [...next];
};
var mainGroups = [
	{
		title: t("groupAppearance"),
		icon: IconPalette,
		key: "appearance",
		fields: [
			{
				type: "select",
				key: "previewMode",
				label: t("labelPreviewMode"),
				binding: "previewMode",
				options: previewModeOptions
			},
			{
				type: "select",
				key: "gridColumns",
				label: t("labelGridColumns"),
				binding: "userGridColumns",
				options: gridColumnOptions,
				onChange: (value) => CoreEvents.emit(AppEvents.GRID_CHANGED, Number(value))
			},
			{
				type: "select",
				key: "cardDensity",
				label: t("labelCardDensity"),
				binding: "cardDensity",
				options: cardDensityOptions
			},
			{
				type: "color",
				key: "accentColor",
				label: t("labelAccentColor"),
				binding: "accentColor",
				options: accentColorOptions
			},
			{
				type: "select",
				key: "language",
				label: t("labelLanguage"),
				binding: "language",
				options: languageOptions
			}
		]
	},
	{
		title: t("groupFilters"),
		icon: IconFilter,
		key: "filters",
		fields: [
			{
				type: "checkbox",
				key: "hideNoMagnet",
				label: t("optionHideNoMagnet"),
				binding: "hideNoMagnet"
			},
			{
				type: "checkbox",
				key: "hideCensored",
				label: t("optionHideCensored"),
				binding: "hideCensored"
			},
			{
				type: "checkbox",
				key: "hideViewed",
				label: t("optionHideViewed"),
				binding: "hideViewed"
			}
		]
	},
	{
		title: t("groupDataHistory"),
		icon: IconClockRotateLeft,
		key: "enhancements",
		fields: [
			{
				type: "checkbox",
				key: "enableMagnets",
				label: t("optionEnableMagnets"),
				binding: "enableMagnets"
			},
			{
				type: "checkbox",
				key: "enableExternalLinks",
				label: t("optionEnableExternalLinks"),
				binding: "enableExternalLinks"
			},
			{
				type: "checkbox",
				key: "enableQuickBar",
				label: t("optionEnableQuickBar"),
				binding: "enableQuickBar"
			},
			{
				type: "checkbox",
				key: "enableHistory",
				label: t("optionEnableHistory"),
				binding: "enableHistory"
			},
			{
				type: "advanced",
				fields: [
					{
						type: "checkbox",
						key: "loadExtraPreviews",
						label: t("optionLoadExtraPreviews"),
						binding: "loadExtraPreviews"
					},
					{
						type: "checkbox",
						key: "showViewedBtn",
						label: t("optionShowViewedBtn"),
						binding: "showViewedBtn"
					},
					{
						type: "checkbox",
						key: "showIdBadge",
						label: t("optionShowIdBadge"),
						binding: "showIdBadge"
					},
					{
						type: "checkbox",
						key: "supjavSortByViews",
						label: t("optionSupjavSortByViews"),
						binding: "supjavSortByViews"
					}
				]
			}
		]
	}
];
var createPortalGridItems = (portals, onSelectionChange) => portals.map((portal) => {
	const enabled = (resolveService(STATE_SERVICE).proxy.enabledPortals || []).includes(portal.id);
	return h("label", {
		className: `fc2-portal-item ${enabled ? "active" : ""}`,
		"data-portal-id": portal.id
	}, h("input", {
		type: "checkbox",
		checked: enabled,
		onchange: (e) => {
			const checked = e.target.checked;
			onSelectionChange(getAllPortalIds(portals).filter((id) => id === portal.id ? checked : (resolveService(STATE_SERVICE).proxy.enabledPortals || []).includes(id)));
		}
	}), h("span", {}, portal.name));
});
var renderSimpleField = (field, ctrl) => {
	switch (field.type) {
		case "checkbox": return CheckboxRow({
			id: field.key,
			label: field.label,
			controller: ctrl,
			binding: field.binding
		});
		case "select": return FormRow({
			label: field.label,
			children: Select({
				id: field.key,
				options: field.options,
				controller: ctrl,
				binding: field.binding,
				onChange: field.onChange
			})
		});
		case "color": return FormRow({
			label: field.label,
			children: ColorPicker({
				id: field.key,
				options: field.options,
				controller: ctrl,
				binding: field.binding
			})
		});
		case "hint": return h("div", { className: "fc2-enh-hint dim" }, field.text);
	}
};
var renderCardActions = (ctrl) => {
	const rows = [
		{
			key: "id",
			label: t("cardActionId")
		},
		{
			key: "viewed",
			label: t("cardActionViewed")
		},
		{
			key: "magnet",
			label: t("cardActionMagnet")
		},
		{
			key: "play",
			label: t("cardActionPlay")
		},
		{
			key: "preview",
			label: t("cardActionPreview")
		},
		{
			key: "external",
			label: t("cardActionExternal")
		}
	].map(({ key, label }) => {
		const row = CheckboxRow({
			id: `card-action-${key}`,
			label,
			checked: getCardPrimaryActions().includes(key),
			onChange: (checked) => toggleCardPrimaryAction(key, checked)
		});
		const input = row.querySelector("input");
		ctrl.listen("cardPrimaryActions", (value) => {
			if (input) input.checked = Array.isArray(value) && value.includes(key);
		});
		return row;
	});
	return h("div", { className: "fc2-settings-card-grid" }, ...rows);
};
var renderField = (field, ctrl) => {
	if (field.type === "advanced") return h("div", { className: "fc2-settings-advanced-block" }, h("div", { className: "fc2-settings-advanced-label" }, t("settingsAdvancedSection")), h("div", { className: "fc2-settings-card-grid" }, ...field.fields.map((f) => renderSimpleField(f, ctrl))));
	if (field.type === "card-actions") return renderCardActions(ctrl);
	return renderSimpleField(field, ctrl);
};
var renderGroup = (config, ctrl) => Card({
	title: config.title,
	icon: config.icon,
	dataGroupKey: config.key,
	children: config.fields.map((f) => renderField(f, ctrl))
});
var renderSideGroup = (config, ctrl) => Card({
	title: config.title,
	icon: config.icon,
	className: config.className,
	dataGroupKey: config.key,
	children: config.fields.map((f) => {
		if (f.type === "portals") {
			const grid = h("div", { className: "fc2-portal-grid" }, ...createPortalGridItems(f.portals, f.onSelectionChange));
			ctrl.listen("enabledPortals", (value) => syncPortalGridState(grid, value || []));
			return [h("div", { className: "fc2-portal-actions" }, Button({
				text: t("btnSelectAll"),
				className: "ghost micro",
				onClick: () => f.onSelectionChange(getAllPortalIds(f.portals))
			}), Button({
				text: t("btnDeselectAll"),
				className: "ghost micro",
				onClick: () => f.onSelectionChange([])
			})), grid];
		}
		if (f.type === "card-actions") return [h("div", { className: "fc2-enh-hint dim" }, t("cardActionDirectHint")), renderCardActions(ctrl)];
		return renderSimpleField(f, ctrl);
	}).flat()
});
var createSettingsTabGrid = (ctrl, portals, onPortalSelectionChange) => {
	const sideGroup = {
		title: t("groupCardActions"),
		icon: IconGear,
		className: "is-advanced",
		fields: [
			{
				type: "hint",
				text: t("cardActionDirectHint")
			},
			{ type: "card-actions" },
			{
				type: "portals",
				portalGrid: h("div"),
				portals,
				onSelectionChange: onPortalSelectionChange
			}
		]
	};
	return h("div", { className: "fc2-settings-grid" }, h("div", { className: "fc2-settings-grid-main" }, ...mainGroups.map((g) => renderGroup(g, ctrl))), h("div", { className: "fc2-settings-grid-side" }, renderSideGroup(sideGroup, ctrl)));
};
var lifecycle = new TabLifecycle();
var updateEnabledPortals = (enabledPortalIds) => {
	resolveService(STATE_SERVICE).proxy.enabledPortals = enabledPortalIds;
	resolveService(PORTAL_SERVICE).clearCache();
};
var renderSettingsTab = exports("renderSettingsTab",() => {
	const ctrl = lifecycle.reset();
	const navigationIntent = consumePendingSettingsNavigationIntent("settings");
	const spotlightGroups = resolveSettingsFocusGroupsFromIntent(navigationIntent);
	const grid = createSettingsTabGrid(ctrl, resolveService(PORTAL_SERVICE).getAllPortals(), updateEnabledPortals);
	const root = h("div", {
		className: `fc2-settings-tab is-advanced-visible`.trim(),
		"data-intent-focus": navigationIntent?.presetKey || ""
	}, grid);
	if (spotlightGroups.length > 0) resolveService(STATE_SERVICE).proxy.settingsCollapsedGroups = [];
	applySettingsCollapsedGroups(grid, resolveService(STATE_SERVICE).proxy.settingsCollapsedGroups || []);
	applySettingsGroupSpotlight(grid, spotlightGroups);
	return root;
});
var onUnmount = exports("onUnmount",() => {
	lifecycle.dispose();
});})}}));
System.register("./TabLifecycle-BwHTz_iJ-BnPxZNF9.js", ['./___monkey.entry.js'],(function(exports){'use strict';var ReactiveController;return{setters:[function(module){ReactiveController=module.R;}],execute:(function(){var TabLifecycle = exports("T",class {
	controller = null;
	cleanups = [];
	reset() {
		this.dispose();
		this.controller = ReactiveController.create();
		return this.controller;
	}
	get ctrl() {
		if (!this.controller) throw new Error("TabLifecycle: reset() must be called before accessing ctrl");
		return this.controller;
	}
	addCleanup(fn) {
		this.cleanups.push(fn);
	}
	dispose() {
		if (this.controller) {
			this.controller.dispose();
			this.controller = null;
		}
		this.cleanups.forEach((fn) => {
			try {
				fn();
			} catch {}
		});
		this.cleanups = [];
	}
});})}}));
System.register("./DebugTab-BvxslCZw-BzciC75p.js", ['./___monkey.entry.js','./components-1yyvCgmd-C1dNzRD5.js','dexie'],(function(exports){'use strict';var consumePendingSettingsNavigationIntent,h,t,DOM_IDS,UIUtils,IconFileExport,LogLevel,exportLogHistory,IconDatabase,Toast,Logger,SCRIPT_INFO,resolveService,STATE_SERVICE,Button,Checkbox;return{setters:[function(module){consumePendingSettingsNavigationIntent=module.c;h=module.h;t=module.t;DOM_IDS=module.y;UIUtils=module.U;IconFileExport=module.a;LogLevel=module.z;exportLogHistory=module.E;IconDatabase=module.l;Toast=module.T;Logger=module.L;SCRIPT_INFO=module.k;resolveService=module.r;STATE_SERVICE=module.S;},function(module){Button=module.B;Checkbox=module.d;},null],execute:(function(){var createEnvironmentSnapshot = () => ({
	userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "unknown",
	language: typeof navigator !== "undefined" ? navigator.language : "unknown",
	url: typeof location !== "undefined" ? location.href : "unknown",
	timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "unknown"
});
var buildLogSummary = (logs) => ({
	total: logs.length,
	errors: logs.filter((e) => e.levelName === "ERROR").length,
	warns: logs.filter((e) => e.levelName === "WARN").length,
	infos: logs.filter((e) => e.levelName === "INFO").length,
	debugs: logs.filter((e) => e.levelName === "DEBUG").length,
	traces: logs.filter((e) => e.levelName === "TRACE").length
});
var buildSupportBundlePayload = (logs, includeFullLogs = false, traceId) => ({
	exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
	version: SCRIPT_INFO.VERSION,
	traceId: traceId?.trim() || null,
	state: {
		syncMode: resolveService(STATE_SERVICE).proxy.syncMode,
		syncStatus: resolveService(STATE_SERVICE).proxy.syncStatus,
		debugMode: resolveService(STATE_SERVICE).proxy.debugMode,
		settingsPreset: resolveService(STATE_SERVICE).proxy.settingsPreset,
		language: resolveService(STATE_SERVICE).proxy.language,
		lastSyncResult: resolveService(STATE_SERVICE).proxy.lastSyncResult
	},
	environment: createEnvironmentSnapshot(),
	logSummary: buildLogSummary(logs),
	logs: includeFullLogs ? logs : []
});
var downloadJsonFile = (filename, payload) => {
	const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
	const url = URL.createObjectURL(blob);
	const anchor = document.createElement("a");
	anchor.href = url;
	anchor.download = filename;
	anchor.click();
	setTimeout(() => {
		URL.revokeObjectURL(url);
		anchor.remove();
	}, 100);
};
var defaultFilters = {
	[LogLevel.ERROR]: true,
	[LogLevel.WARN]: true,
	[LogLevel.INFO]: true,
	[LogLevel.DEBUG]: false,
	[LogLevel.TRACE]: false
};
var container = null;
var listContainer = null;
var traceFilterInput = null;
var activeFilters = { ...defaultFilters };
var getTraceId = () => traceFilterInput?.value.trim() || "";
var getFilteredLogs = () => {
	const traceId = getTraceId();
	return exportLogHistory({ traceId: traceId || void 0 }).filter((entry) => {
		const matchesLevel = !!activeFilters[entry.level];
		const matchesTraceId = !traceId || entry.traceId === traceId;
		return matchesLevel && matchesTraceId;
	});
};
var refresh = () => {
	if (!listContainer) return;
	listContainer.textContent = "";
	getFilteredLogs().reverse().forEach((entry) => listContainer.appendChild(createDebugLogItem(entry)));
};
var downloadLogs = (includeFullLogs) => {
	const traceId = getTraceId();
	downloadJsonFile("fc2-debug.json", buildSupportBundlePayload(getFilteredLogs(), includeFullLogs, traceId));
};
var createDebugLogItem = (entry) => {
	const countTag = entry.count && entry.count > 1 ? ` x${entry.count}` : "";
	const item = h("div", { className: `fc2-log-item level-${(entry.levelName || "INFO").toLowerCase()}` }, h("span", { className: "fc2-log-time" }, `[${entry.timestamp}]`), h("span", { className: "fc2-log-level" }, `${entry.levelName}${countTag}`), h("span", { className: "fc2-log-module" }, `[${entry.module}]`), h("span", { className: "fc2-log-msg" }, entry.message), entry.traceId ? h("span", { className: "fc2-log-trace" }, `#${entry.traceId}`) : null);
	if (entry.data) {
		const dataView = h("pre", { className: "fc2-log-payload fc2-is-hidden" }, JSON.stringify(entry.data, null, 2));
		const toggle = h("button", {
			type: "button",
			className: "fc2-log-payload-toggle",
			onclick: () => dataView.classList.toggle("fc2-is-hidden")
		}, UIUtils.icon(IconDatabase), ` ${t("debugPayload")}`);
		item.append(toggle, dataView);
	}
	return item;
};
var renderDebugTab = exports("renderDebugTab",() => {
	const navigationIntent = consumePendingSettingsNavigationIntent("debug");
	traceFilterInput = h("input", {
		className: "fc2-enh-input",
		placeholder: t("debugTraceIdPlaceholder"),
		oninput: refresh
	});
	container = h("div", {
		className: "fc2-debug-container",
		"data-intent-focus": navigationIntent?.presetKey || ""
	}, h("div", { className: "fc2-debug-header" }, h("div", { className: "fc2-debug-actions" }, Button({
		icon: UIUtils.icon(IconFileExport),
		text: t("btnCopyAll"),
		onClick: () => {
			navigator.clipboard.writeText(getFilteredLogs().map((e) => `[${e.timestamp}] [${e.levelName}] [${e.module}] ${e.message}`).join("\\n"));
			Toast.show(t("alertLogsCopied"), "success");
		}
	}), Button({
		text: t("debugExportJson"),
		onClick: () => downloadLogs(false)
	}), Button({
		text: t("btnClearLogs"),
		className: "danger",
		onClick: () => {
			Logger.clear();
			refresh();
			Toast.show(t("alertLogsCleared"), "success");
		}
	})), h("div", { className: "fc2-debug-filters" }, h("span", { className: "fc2-label-dim" }, t("labelLogFilters")), traceFilterInput, ...[
		LogLevel.ERROR,
		LogLevel.WARN,
		LogLevel.INFO,
		LogLevel.DEBUG,
		LogLevel.TRACE
	].map((level) => Checkbox({
		id: `filter-${level}`,
		label: LogLevel[level],
		checked: activeFilters[level],
		onChange: (val) => {
			activeFilters[level] = val;
			refresh();
		}
	})))), listContainer = h("div", {
		id: DOM_IDS.LOG_LIST,
		className: "fc2-log-list-container"
	}));
	refresh();
	return container;
});
var onUnmount = exports("onUnmount",() => {
	container = null;
	listContainer = null;
	traceFilterInput = null;
	activeFilters = { ...defaultFilters };
});})}}));
System.register("./AboutTab-BYbIrdck-YaaPEqkJ.js", ['./___monkey.entry.js','./components-1yyvCgmd-C1dNzRD5.js','dexie'],(function(exports){'use strict';var h,SCRIPT_INFO,t,IconTriangleExclamation,IconCircleInfo,Card;return{setters:[function(module){h=module.h;SCRIPT_INFO=module.k;t=module.t;IconTriangleExclamation=module.F;IconCircleInfo=module.G;},function(module){Card=module.C;},null],execute:(function(){/**
* Render localized HTML content safely.
* Uses h() with innerHTML which auto-sanitizes via DomUtils.
*/
var safeContent = (htmlStr, className) => h("div", {
	className,
	innerHTML: htmlStr
});
var renderAboutTab = exports("renderAboutTab",() => {
	return h("div", { className: "fc2-about-tab" }, h("div", { className: "fc2-about-header" }, h("h2", {}, SCRIPT_INFO.NAME), h("div", { className: "fc2-version-badge" }, `v${SCRIPT_INFO.VERSION}`), h("p", { className: "fc2-about-desc" }, t("aboutDescription"))), Card({
		title: t("aboutHelpTitle"),
		icon: IconCircleInfo,
		children: safeContent(t("aboutHelpContent"), "fc2-about-content")
	}), Card({
		title: t("tabDmca"),
		icon: IconTriangleExclamation,
		className: "warning",
		children: safeContent(t("dmcaContent"), "fc2-about-content dmca")
	}), h("div", { className: "fc2-about-footer" }, h("a", {
		href: SCRIPT_INFO.GREASYFORK_URL,
		target: "_blank",
		rel: "noopener noreferrer",
		className: "fc2-link"
	}, t("labelGreasyFork")), h("span", { className: "fc2-dim" }, " | "), h("span", { className: "fc2-dim" }, t("aboutFooterTagline"))));
});})}}));
System.register("./components-1yyvCgmd-C1dNzRD5.js", ['./___monkey.entry.js'],(function(exports){'use strict';var h,UIUtils,IconEye,IconEyeSlash,resolveService,STATE_SERVICE;return{setters:[function(module){h=module.h;UIUtils=module.U;IconEye=module.J;IconEyeSlash=module.K;resolveService=module.r;STATE_SERVICE=module.S;}],execute:(function(){var resolveIcon$1 = (icon) => typeof icon === "string" ? UIUtils.icon(icon) : icon;
var Button = exports("B",(props) => {
	const { text, onClick, className = "", icon, title, disabled, style, variant = "primary", size = "md" } = props;
	const btn = h("button", {
		type: "button",
		className: `fc2-enh-btn fc2-enh-btn-${variant} fc2-enh-btn-size-${size} ${className}`.trim(),
		title: title || "",
		disabled: !!disabled,
		onclick: (e) => {
			const result = onClick(e);
			if (result instanceof Promise) result.catch((e) => console.error("UI", "Button action error", e));
		}
	}, icon ? resolveIcon$1(icon) : null, text ? h("span", { className: "fc2-btn-text" }, text) : null);
	if (style) Object.assign(btn.style, style);
	return btn;
});
var resolveIcon = (icon) => typeof icon === "string" ? UIUtils.icon(icon) : icon;
var Card = exports("C",(props) => {
	const { title, subtitle, icon, children, className = "", collapsed, dataGroupKey } = props;
	const body = h("div", { className: "fc2-panel-card-body" }, ...Array.isArray(children) ? children : [children]);
	return h("div", {
		className: `fc2-panel-card${collapsed ? " is-collapsed" : ""} ${className}`.trim(),
		"data-settings-group": dataGroupKey
	}, h("h4", { className: "fc2-panel-card-heading" }, icon ? resolveIcon(icon) : null, h("span", { className: "fc2-card-title" }, title), subtitle ? h("div", { className: "fc2-card-subtitle" }, subtitle) : null), body);
});
var Checkbox = exports("d",(props) => {
	const { id, label, checked, onChange, controller, binding } = props;
	const checkbox = h("input", {
		id: `set-${id}`,
		type: "checkbox",
		checked: checked || false,
		onchange: !controller || !binding ? (e) => {
			const isChecked = e.target.checked;
			if (onChange) onChange(isChecked);
		} : void 0
	});
	const container = h("label", {
		className: "fc2-enh-checkbox-label",
		htmlFor: `set-${id}`
	}, checkbox, h("span", { className: "fc2-enh-checkbox-text" }, label));
	if (controller && binding) controller.bind(checkbox, {
		key: binding,
		mode: "checked"
	});
	return container;
});
var ColorPicker = exports("c",({ id, options, controller, binding, onChange }) => {
	const container = h("div", {
		className: "fc2-color-picker",
		id
	});
	const updateSelection = (value) => {
		container.querySelectorAll(".fc2-color-option").forEach((el) => {
			const elValue = el.getAttribute("data-value");
			el.classList.toggle("is-active", elValue === value);
		});
	};
	options.forEach((option) => {
		const item = h("button", {
			type: "button",
			className: "fc2-color-option",
			title: option.label,
			dataset: { value: option.value },
			style: { "--option-color": option.value }
		});
		item.addEventListener("click", () => {
			const value = option.value;
			if (binding) resolveService(STATE_SERVICE).proxy[binding] = value;
			else updateSelection(value);
			onChange?.(value);
		});
		container.appendChild(item);
	});
	if (controller && binding) {
		const initialValue = resolveService(STATE_SERVICE).proxy[binding];
		updateSelection(initialValue);
		controller.listen(binding, (val) => updateSelection(val));
	}
	return container;
});
var FormRow = exports("F",(props) => {
	const { label, children, className = "" } = props;
	const childArray = Array.isArray(children) ? children : [children];
	return h("div", { className: `fc2-enh-form-row ${className}` }, label ? h("label", { className: "fc2-enh-label" }, label) : null, ...childArray);
});
var CheckboxRow = exports("b",(props) => {
	const { className = "" } = props;
	const checkboxLabel = Checkbox(props);
	checkboxLabel.className = `${checkboxLabel.className} fc2-enh-form-row checkbox ${className}`;
	return checkboxLabel;
});
var Input = exports("I",(props) => {
	const { id, type, value, placeholder, validator, onChange, controller, binding } = props;
	const input = h("input", {
		id: `set-${id}`,
		className: "fc2-enh-input",
		type: type === "number" ? "text" : type,
		inputMode: type === "number" ? "numeric" : void 0,
		pattern: type === "number" ? "[0-9]*" : void 0,
		value: value || "",
		placeholder: placeholder || "",
		oninput: (e) => {
			const target = e.target;
			const newValue = target.value;
			if (validator) {
				const result = validator(newValue);
				if (!result.valid) {
					target.classList.add("invalid");
					target.title = result.error || "Invalid input";
				} else {
					target.classList.remove("invalid");
					target.title = "";
				}
			}
			if (!controller || !binding) {
				if (onChange) onChange(newValue);
			}
		},
		autocomplete: "off",
		spellcheck: false
	});
	if (controller && binding) controller.bind(input, {
		key: binding,
		mode: "value",
		onChange: onChange ? (v) => onChange(v) : void 0
	});
	return input;
});
var renderToggleIcon = (isPassword) => UIUtils.icon(isPassword ? IconEye : IconEyeSlash);
var PasswordInput = exports("P",(props) => {
	const input = Input({
		...props,
		type: "password"
	});
	const toggle = h("button", {
		className: "fc2-input-toggle",
		type: "button",
		"aria-label": "Toggle password visibility",
		onclick: () => {
			const isPass = input.type === "password";
			input.type = isPass ? "text" : "password";
			toggle.textContent = "";
			toggle.appendChild(renderToggleIcon(!isPass));
		}
	});
	toggle.appendChild(renderToggleIcon(true));
	return h("div", { className: "fc2-input-group" }, input, toggle);
});
var Select = exports("a",(props) => {
	const { id, options, value, onChange, controller, binding } = props;
	const select = h("select", {
		id: `set-${id}`,
		className: "fc2-enh-select",
		onchange: !controller || !binding ? (e) => {
			const newValue = e.target.value;
			if (onChange) onChange(newValue);
		} : void 0
	}, ...options.map((opt) => h("option", {
		value: opt.value,
		selected: value !== void 0 ? opt.value === value : false
	}, opt.label)));
	if (controller && binding) controller.bind(select, {
		key: binding,
		mode: "value",
		onChange: onChange ? (v) => onChange(v) : void 0
	});
	return select;
});
var StatItem = exports("S",(props) => {
	const { id, label, value, unit } = props;
	const valContainer = h("div", {
		className: "fc2-stat-value",
		id: id ? `stat-${id}` : void 0
	});
	if (value instanceof HTMLElement) valContainer.appendChild(value);
	else valContainer.textContent = String(value);
	if (unit) valContainer.appendChild(h("span", { className: "fc2-stat-unit" }, unit));
	return h("div", { className: "fc2-stat-item" }, h("div", { className: "fc2-stat-label" }, label), valContainer);
});})}}));
System.import("./___monkey.entry.js", "./");