FC2PPVDB Enhanced

为 FC2PPVDB、Supjav 等站点注入磁力链接聚合、高清封面替换、画廊模式、历史记录同步等功能。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name            FC2PPVDB Enhanced
// @name:en         FC2PPVDB Enhanced
// @namespace       https://greasyfork.org/zh-CN/scripts/552583-fc2ppvdb-enhanced
// @version         2.0.5
// @author          Icarusle
// @description     为 FC2PPVDB、Supjav 等站点注入磁力链接聚合、高清封面替换、画廊模式、历史记录同步等功能。
// @description:en  Inject magnet links, HD covers, gallery mode, and history sync for FC2PPVDB, Supjav, and more.
// @license         MIT
// @icon            https://fc2ppvdb.com/favicon.ico
// @match           https://fc2ppvdb.com/*
// @match           https://fd2ppv.cc/*
// @match           https://supjav.com/*
// @match           https://missav.ws/*
// @match           https://missav.ai/*
// @match           https://javdb.com/*
// @match           https://javdb565.com/*
// @require         https://unpkg.com/[email protected]/dist/dexie.js
// @connect         sukebei.nyaa.si
// @connect         wumaobi.com
// @connect         fourhoi.com
// @connect         fd2ppv.cc
// @connect         fc2ppvdb.com
// @connect         supabase.co
// @connect         0cili.eu
// @connect         www.javbus.com
// @connect         www.javlibrary.com
// @connect         www.dmm.co.jp
// @connect         adult.contents.fc2.com
// @grant           GM_addStyle
// @grant           GM_deleteValue
// @grant           GM_getValue
// @grant           GM_info
// @grant           GM_registerMenuCommand
// @grant           GM_setClipboard
// @grant           GM_setValue
// @grant           GM_unregisterMenuCommand
// @grant           GM_xmlhttpRequest
// @grant           unsafeWindow
// @run-at          document-end
// @noframes
// ==/UserScript==

(function (Dexie) {
  'use strict';

  const SCRIPT_INFO = {
    NAME: "FC2PPVDB Enhanced",
    VERSION: typeof GM_info !== "undefined" ? GM_info.script.version : "2.0.5",
    NAMESPACE: "https://greasyfork.org/zh-CN/scripts/552583-fc2ppvdb-enhanced",
    GREASYFORK_URL: "https://greasyfork.org/scripts/552583"
  };
  const STORAGE_KEYS = {
    SETTINGS: "settings_v1",
    CACHE: "magnet_cache_v1",
    HISTORY: "history_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",
    CURRENT_USER_EMAIL: "current_user_email",
    LAST_SYNC_TS: "last_sync_ts",
    LAST_AUTO_SYNC_TS: "last_auto_sync_ts",
    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"
  };
  const DATABASE = {
    NAME: "fc2_enhanced_db"
  };
  const CACHE = {
    MEMORY_MAX_SIZE: 1e3,
    MEMORY_EXPIRATION_MS: 5 * 60 * 1e3,
    EXPIRATION_MS: 14 * 24 * 60 * 60 * 1e3,
    PREVIEW_MAX_SIZE: 5,
    PREVIEW_PRELOAD_LIMIT: 30
  };
  const UI_CONSTANTS = {
    FONT_FAMILY: "'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', 'Meiryo', sans-serif",
    Z_INDEX_MAX: 2147483647,
    Z_INDEX_OVERLAY: 2147483646,
    Z_INDEX_TOOLTIP: 999999,
    DEFAULT_TIMESTAMP: "1970-01-01T00:00:00.000Z",
    DEFAULT_SYNC_FILENAME: "fc2_enhanced_sync.json",
    TIME_ZONE: "Asia/Shanghai",
    PLACEHOLDER_IMAGE: "https://placehold.co/400x300?text=No+Image",
    SWIPE_DISMISS_THRESHOLD: 100
};
  const UI_TOKENS = {
    COLORS: {
      SUCCESS: "#34d399",
      ERROR: "#f87171",
      WARN: "#fab387",
      INFO: "#89b4fa",
      WHITE: "#f4f4f7"
    },
    BACKDROP: {
      BLUR: "12px",
      SHADOW: "0 8px 16px rgba(0,0,0,0.4)"
    },
    SPACING: {
      XS: "4px",
      MD: "12px"
    },
    RADIUS: {
      MD: "12px"
    }
  };
  const DOM_IDS = {
    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"
  };
  const CSS_CLASSES = {
    cardRebuilt: "card-rebuilt",
    processedCard: "processed-card",
    hideNoMagnet: "hide-no-magnet",
    videoPreviewContainer: "video-preview-container",
    staticPreview: "static-preview",
    previewElement: "preview-element",
    hidden: "hidden",
    infoArea: "info-area",
    customTitle: "custom-card-title",
    fc2IdBadge: "fc2-id-badge",
    badgeCopied: "copied",
    preservedIconsContainer: "preserved-icons-container",
    resourceLinksContainer: "resource-links-container",
    resourceBtn: "resource-btn",
    btnLoading: "is-loading",
    btnMagnet: "magnet",
    tooltip: "tooltip",
    buttonText: "button-text",
    extraPreviewContainer: "preview-container",
    extraPreviewTitle: "preview-title",
    extraPreviewGrid: "preview-grid",
    isCensored: "is-censored",
    hideCensored: "hide-censored",
    isViewed: "is-viewed",
    hideViewed: "hide-viewed",
    isWanted: "is-wanted",
    isDownloaded: "is-downloaded",
    isBlocked: "is-blocked",
    hideBlocked: "hide-blocked"
  };
  const NETWORK = {
    CHUNK_SIZE: 12
  };
  const TIMING = {
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,
    RELOAD_DELAY_FAST: 800,
    RELOAD_DELAY_NORMAL: 1500,
SCRIPT_INJECTION_DELAY: 400,
SYNC_DEBOUNCE_MS: 2e3,
    SYNC_INIT_DELAY: 5e3,
    MAGNET_RANDOM_DELAY_MS: 3e3,
    CLOUDFLARE_BACKOFF_MS: 6e4,
    FD2_BACKOFF_MS: 3e5,
    RATE_LIMIT_BACKOFF_MS: 6e4,
    OCILI_DELAY_MS: 2e3,
    OCILI_RANDOM_DELAY_MS: 2e3,
    ACTRESS_BASE_DELAY_MS: 1500,
    ACTRESS_RANDOM_DELAY_MS: 1500,
    POLITE_DELAY_MS: 500,
    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
  };
  const CLOUDFLARE_INDICATORS = ["Just a moment...", "Checking your browser", "Attention Required!", "Cloudflare"];
  const 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
  };
  const 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",
    FC2PPVDB: "https://fc2ppvdb.com/articles/{id}",
    FD2PPV: "https://fd2ppv.cc/articles/{id}",
    SUKEBEI: "https://sukebei.nyaa.si/?f=0&c=0_0&q={id}",
    GOOGLE_LENS: "https://lens.google.com/uploadbyurl?url={url}",
    FOURHOI_COVER: "https://fourhoi.com/fc2-ppv-{id}/cover-t.jpg",
    FOURHOI_BASE: "https://fourhoi.com",
    WUMAOBI_COVER: "https://wumaobi.com/fc2daily/data/FC2-PPV-{id}/cover.jpg"
  };
  const SCRAPER_CONFIG = {
    MAX_CONCURRENT_BATCHES: 4
  };
  const SCRAPER_URLS = {
    SUKEBEI_SEARCH: "https://sukebei.nyaa.si/?f=0&c=0_0&q={query}&s=seeders&o=desc",
    OCILI_SEARCH: "https://0cili.eu/search?q={query}",
    WUMAOBI_DETAIL: "https://wumaobi.com/fc2daily/detail/FC2-PPV-{id}",
    WUMAOBI_BASE: "https://wumaobi.com"
  };
  const MAGNET_CONFIG = {
    MAX_CONCURRENCY: 4,
    MAX_RETRIES: 2,
    RETRY_DELAY: 1e3,
    PREDICTIVE_LIMIT: 12,
    DEFAULT_TYPE: "fc2",
    SEARCH_TIMEOUT_MS: 6e4
  };
  const PREVIEW_BLACKLIST = ["moechat_ads.jpg", "mc.yandex.ru", "linglan_ad1.jpg"];
  const JAV_PREFIX_BLACKLIST = ["FC2", "PPV", "PAGE", "LIST", "NEW", "BEST", "FILE", "VIEW"];
  const ACTRESS_BLACKLIST_STRINGS = [
    "首页",
    "分类",
    "我的",
    "搜索",
    "排行榜",
    "导航",
    "菜单",
    "更多",
    "全部",
    "女优",
    "无码",
    "有码",
    "素人",
    "流出",
    "破解",
    "解密",
    "合集",
    "个人拍摄",
    "個人撮影"
  ];
  const ACTRESS_BLACKLIST = [
    /首页/,
    /分类/,
    /我的/,
    /搜索/,
    /排行榜/,
    /导航/,
    /菜单/,
    /更多/,
    /全部/,
    /女优/,
    /无码/,
    /有码/,
    /素人/,
    /流出/,
    /破解/,
    /解密/,
    /合集/,
    /个人拍摄/,
    /個人撮影/,
    /Top\s*\d+/i,
    /^[\d\s]+$/
  ];
  const PATTERNS = {
    FC2_PPV_PREFIX: "FC2-PPV-",
    WUMAOBI_COVER: "cover.jpg",
    WUMAOBI_MAIN: "main.jpg",
    CENSORED_INDICATOR: "icon-mosaic_free color_free0"
  };
  const SYNC_STATUS = {
    IDLE: "idle",
    SYNCING: "syncing",
    SUCCESS: "success",
    ERROR: "error",
    CONFLICT: "conflict"
  };
  const SUPABASE_ENDPOINTS = {
    TOKEN: "/auth/v1/token",
    SIGNUP: "/auth/v1/signup",
    USER_HISTORY: "/rest/v1/user_history"
  };
  const SYSTEM_KEYS = {
    MESSAGING_CHANNEL: "fc2-enhanced-sync"
  };
  const MAX_HISTORY = 500;
  const PREFIX = "[FC2-ENH]";
  const DEDUP_WINDOW_MS = 2e3;
  const THROTTLE_WINDOW_MS = 5e3;
  const THROTTLE_BURST = 3;
  var LogLevel = ((LogLevel2) => {
    LogLevel2[LogLevel2["SILENT"] = 0] = "SILENT";
    LogLevel2[LogLevel2["ERROR"] = 1] = "ERROR";
    LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
    LogLevel2[LogLevel2["INFO"] = 3] = "INFO";
    LogLevel2[LogLevel2["DEBUG"] = 4] = "DEBUG";
    LogLevel2[LogLevel2["TRACE"] = 5] = "TRACE";
    LogLevel2[LogLevel2["NONE"] = 0] = "NONE";
    LogLevel2[LogLevel2["SUCCESS"] = 3] = "SUCCESS";
    return LogLevel2;
  })(LogLevel || {});
  const LEVEL_NAMES = {
    [
      0
]: "SILENT",
    [
      1
]: "ERROR",
    [
      2
]: "WARN",
    [
      3
]: "INFO",
    [
      4
]: "DEBUG",
    [
      5
]: "TRACE"
  };
  const formatTime = () => {
    const d = new Date();
    const hh = String(d.getHours()).padStart(2, "0");
    const mm = String(d.getMinutes()).padStart(2, "0");
    const ss = String(d.getSeconds()).padStart(2, "0");
    const ms = String(d.getMilliseconds()).padStart(3, "0");
    return `${hh}:${mm}:${ss}.${ms}`;
  };
  const makeKey = (level, module, message) => `${level}|${module}|${message}`;
  class LoggerImpl {
    constructor() {
      this._enabled = false;
      this._history = [];
      this._level = 2;
      this._timers = new Map();
      this._traceCounter = 0;
      this._groupDepth = 0;
      this._scopeCache = new Map();
      this._dedup = new Map();
      this._dedupFlushTimer = null;
      this._throttle = 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 {
        const saved = typeof GM_getValue !== "undefined" ? GM_getValue("fc2_debug_mode", false) : false;
        if (saved) this.enable(false);
      } catch {
      }
    }
    enable(persist = true) {
      this._enabled = true;
      this._level = 5;
      if (persist) {
        try {
          if (typeof GM_setValue !== "undefined") GM_setValue("fc2_debug_mode", true);
        } catch {
        }
      }
    }
    disable(persist = true) {
      this._enabled = false;
      this._level = 2;
      if (persist) {
        try {
          if (typeof GM_setValue !== "undefined") GM_setValue("fc2_debug_mode", false);
        } catch {
        }
      }
    }
    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: (msg, data, traceId) => this.error(module, msg, data, traceId),
          warn: (msg, data, traceId) => this.warn(module, msg, data, traceId),
          info: (msg, data, traceId) => this.info(module, msg, data, traceId),
          debug: (msg, data, traceId) => this.debug(module, msg, data, traceId),
          trace: (msg, data, traceId) => this.trace(module, msg, data, traceId),
          success: (msg, data, traceId) => this.info(module, msg, 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 void 0;
      this._timers.delete(label);
      const elapsed = (performance.now() - start).toFixed(2);
      const msg = `${label}: ${elapsed}ms`;
      this._push(4, "Timer", msg);
      return msg;
    }
log(module, message, data, traceId) {
      this._push(3, module, message, data, traceId);
    }
    info(module, message, data, traceId) {
      this._push(3, module, message, data, traceId);
    }
    warn(module, message, data, traceId) {
      this._push(2, module, message, data, traceId);
    }
    error(module, message, data, traceId) {
      this._push(1, module, message, data, traceId);
    }
success(module, message, data, traceId) {
      this._push(3, module, message, data, traceId);
    }
    debug(module, message, data, traceId) {
      this._push(4, module, message, data, traceId);
    }
    trace(module, message, data, traceId) {
      this._push(5, module, message, data, traceId);
    }
group(module, label, traceId) {
      this._groupDepth++;
      this._push(3, module, `[group:start] ${label}`, void 0, traceId);
      if (this._shouldOutput(
        3
)) {
        console.group(`${PREFIX} [${module}] ${label}`);
      }
    }
    groupEnd() {
      if (this._groupDepth <= 0) return;
      this._groupDepth--;
      if (this._enabled) {
        console.groupEnd();
      }
    }
_shouldOutput(level) {
      return level <= this._level;
    }
    _push(level, module, message, data, traceId) {
      if (level > this._level && level !== 1) return;
      const key = makeKey(level, module, message);
      if (level >= 4) {
        if (this._checkThrottle(key)) return;
      }
      const existing = this._dedup.get(key);
      if (existing && Date.now() - existing.firstTs < DEDUP_WINDOW_MS) {
        existing.count++;
        existing.lastEntry.count = existing.count;
        if (data !== void 0) existing.lastEntry.data = data;
        this._scheduleDedupFlush();
        return;
      }
      this._flushDedup();
      const entry = {
        level,
        levelName: LEVEL_NAMES[level] ?? "UNKNOWN",
        module,
        message,
        timestamp: formatTime(),
        count: 1,
        ...traceId ? { traceId } : {},
        ...data !== void 0 ? { data } : {}
      };
      this._dedup.set(key, {
        count: 1,
        firstTs: Date.now(),
        lastEntry: entry,
        flushed: false
      });
      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) {
        if (state.count > 1 && !state.flushed) {
          state.flushed = true;
          state.lastEntry.count = state.count;
          if (this._shouldOutput(state.lastEntry.level)) {
            const tag = this._formatTag(state.lastEntry);
            console.debug(`${tag} (x${state.count}) ${state.lastEntry.message}`);
          }
        }
      }
      const now = Date.now();
      for (const [key, state] of this._dedup) {
        if (now - state.firstTs > DEDUP_WINDOW_MS) {
          this._dedup.delete(key);
        }
      }
    }
_checkThrottle(key) {
      const now = Date.now();
      let state = this._throttle.get(key);
      if (!state || now - state.windowStart > THROTTLE_WINDOW_MS) {
        state = { count: 1, windowStart: now, suppressed: 0 };
        this._throttle.set(key, state);
        return false;
      }
      state.count++;
      if (state.count <= THROTTLE_BURST) return false;
      state.suppressed++;
      if (state.suppressed === 1) {
        const capturedState = state;
        setTimeout(
          () => {
            if (capturedState.suppressed > 0) {
              const entry = {
                level: 4,
                levelName: "DEBUG",
                module: "Throttle",
                message: `Suppressed ${capturedState.suppressed} repeated messages in ${THROTTLE_WINDOW_MS}ms`,
                timestamp: formatTime(),
                count: 1
              };
              this._record(entry);
              if (this._shouldOutput(
                4
)) {
                console.debug(this._formatTag(entry), entry.message);
              }
            }
            this._throttle.delete(key);
          },
          THROTTLE_WINDOW_MS - (now - capturedState.windowStart)
        );
      }
      return true;
    }
_record(entry) {
      this._history.push(entry);
      if (this._history.length > MAX_HISTORY) {
        this._history.splice(0, this._history.length - MAX_HISTORY);
      }
    }
_formatTag(entry) {
      const traceTag = entry.traceId ? ` [${entry.traceId}]` : "";
      return `${PREFIX}${traceTag} [${entry.levelName}] [${entry.module}]`;
    }
    _output(entry) {
      if (!this._shouldOutput(entry.level)) return;
      const tag = this._formatTag(entry);
      const args = [tag, entry.message];
      if (entry.data !== void 0) args.push(entry.data);
      switch (entry.level) {
        case 1:
          console.error(...args);
          break;
        case 2:
          console.warn(...args);
          break;
        case 4:
        case 5:
          console.debug(...args);
          break;
        default:
          console.log(...args);
          break;
      }
    }
  }
  const Logger = new LoggerImpl();
  const log$D = Logger.scope("Container");
  class Container {
    constructor() {
      this.services = new Map();
      this.initialized = false;
    }
register(name, service) {
      if (this.services.has(name)) {
        log$D.warn(`Service ${name} already registered, overwriting`);
      }
      this.services.set(name, service);
      return service;
    }
get(name) {
      const service = this.services.get(name);
      if (!service) {
        throw new Error(`[Container] Service not found: ${name}`);
      }
      return service;
    }
async bootstrap() {
      if (this.initialized) return;
      Logger.group("Container", "System bootstrap");
      try {
        log$D.debug("Stage 1: Initializing services");
        for (const [name, service] of this.services) {
          if (service.onInit) {
            try {
              await service.onInit();
              log$D.debug(`Initialized: ${name}`);
            } catch (error) {
              log$D.error(`Failed to initialize service: ${name}`, error);
            }
          }
        }
        log$D.debug("Stage 2: Orchestrating bootstrap");
        for (const [name, service] of this.services) {
          if (service.onBootstrap) {
            try {
              await service.onBootstrap();
              log$D.debug(`Bootstrapped: ${name}`);
            } catch (error) {
              log$D.error(`Failed to bootstrap service: ${name}`, error);
            }
          }
        }
        this.initialized = true;
        log$D.info("System bootstrap complete");
      } catch (error) {
        log$D.error("Bootstrap failed", error);
        throw error;
      } finally {
        Logger.groupEnd();
      }
    }
async shutdown() {
      log$D.info("Shutting down services");
      for (const service of this.services.values()) {
        if (service.onCleanup) await service.onCleanup();
      }
      this.services.clear();
      this.initialized = false;
    }
  }
  const AppContainer = new Container();
  const log$C = Logger.scope("Events");
  var AppEvents = ((AppEvents2) => {
    AppEvents2["BOOTSTRAP"] = "app:bootstrap";
    AppEvents2["SERVICES_READY"] = "app:services-ready";
    AppEvents2["UI_READY"] = "app:ui-ready";
    AppEvents2["STATE_CHANGED"] = "app:state-changed";
    AppEvents2["THEME_CHANGED"] = "ui:theme-changed";
    AppEvents2["LANGUAGE_CHANGED"] = "ui:language-changed";
    AppEvents2["GRID_CHANGED"] = "ui:grid-changed";
    AppEvents2["SYNC_STATUS_CHANGED"] = "sync:status-changed";
    AppEvents2["HISTORY_LOADED"] = "history:loaded";
    AppEvents2["HISTORY_ADDED"] = "history:added";
    AppEvents2["HISTORY_REMOVED"] = "history:removed";
    AppEvents2["HISTORY_CLEARED"] = "history:cleared";
    AppEvents2["SITE_READY"] = "site:ready";
    AppEvents2["MAGNET_FOUND"] = "magnet:found";
    AppEvents2["MAGNET_FAILED"] = "magnet:failed";
    AppEvents2["CARD_READY"] = "card:ready";
    AppEvents2["VIEW_STATE_CHANGED"] = "view:state-changed";
    AppEvents2["COLLECTION_HEALTH_PROGRESS"] = "collection:health-progress";
    AppEvents2["COLLECTION_UPDATED"] = "collection:updated";
    AppEvents2["COLLECTION_STATS_CHANGED"] = "collection:stats-changed";
    AppEvents2["COLLECTION_EXPORT"] = "collection:export";
    AppEvents2["COLLECTION_IMPORT"] = "collection:import";
    AppEvents2["SHOW_TOAST"] = "ui:show-toast";
    AppEvents2["OPEN_SETTINGS"] = "ui:open-settings";
    AppEvents2["PANEL_OPENED"] = "ui:panel-opened";
    AppEvents2["PANEL_CLOSED"] = "ui:panel-closed";
    AppEvents2["HISTORY_CHANGED"] = "history:changed";
    return AppEvents2;
  })(AppEvents || {});
  class CoreEventsImpl {
    constructor() {
      this.handlers = new Map();
    }
on(event, handler) {
      if (!this.handlers.has(event)) {
        this.handlers.set(event, 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$C.trace(`Emit: ${event}`, data);
      const handlersSet = this.handlers.get(event);
      if (handlersSet) {
        handlersSet.forEach((handler) => {
          try {
            handler(data);
          } catch (e) {
            log$C.error(`Handler error for ${event}`, e);
          }
        });
      }
    }
  }
  const CoreEvents = new CoreEventsImpl();
  const Config = {
    EXTERNAL_URLS,
    SCRAPER_URLS,
    SCRAPER_CONFIG,
    STORAGE_KEYS,
    TIMEOUTS: { API: 2e4, VIDEO_LOAD: 5e3, SYNC_DEBOUNCE: 2e3 },
    CLASSES: CSS_CLASSES,
    CACHE_EXPIRATION_DAYS: 14,
    COPIED_BADGE_DURATION: 1500
  };
  class CryptoServiceImpl {
    async getSalt(keyStr) {
      const encoder = new TextEncoder();
      const data = encoder.encode(keyStr);
      const hash = await crypto.subtle.digest("SHA-256", data);
      return new Uint8Array(hash).slice(0, 16);
    }
    async getKey(password) {
      const encoder = new TextEncoder();
      const keyMaterial = await crypto.subtle.importKey("raw", encoder.encode(password), { name: "PBKDF2" }, false, [
        "deriveKey"
      ]);
      const salt = await this.getSalt(password);
      return await crypto.subtle.deriveKey(
        {
          name: "PBKDF2",
          salt,
          iterations: 1e5,
          hash: "SHA-256"
        },
        keyMaterial,
        { name: "AES-GCM", length: 256 },
        true,
        ["encrypt", "decrypt"]
      );
    }
    async encrypt(text, password) {
      if (!password) password = String(GM_info.script.version);
      try {
        const key = await this.getKey(password);
        const iv = crypto.getRandomValues(new Uint8Array(12));
        const encoder = new TextEncoder();
        const encryptedContent = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encoder.encode(text));
        const encryptedBuffer = new Uint8Array(encryptedContent);
        const combined = new Uint8Array(iv.length + encryptedBuffer.length);
        combined.set(iv);
        combined.set(encryptedBuffer, iv.length);
        let binary = "";
        const chunkSize = 8192;
        for (let i = 0; i < combined.length; i += chunkSize) {
          binary += String.fromCharCode(...combined.subarray(i, i + chunkSize));
        }
        return btoa(binary);
      } catch (error) {
        Logger.error("Crypto", "Encryption failed", error);
        throw new Error("Encryption failed");
      }
    }
    async decrypt(encryptedText, password) {
      if (!password) password = String(GM_info.script.version);
      try {
        const combinedStr = atob(encryptedText);
        const combined = new Uint8Array(combinedStr.length);
        for (let i = 0; i < combinedStr.length; i++) {
          combined[i] = combinedStr.charCodeAt(i);
        }
        const iv = combined.slice(0, 12);
        const encryptedBuffer = combined.slice(12);
        const key = await this.getKey(password);
        const decryptedContent = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, encryptedBuffer);
        const decoder = new TextDecoder();
        return decoder.decode(decryptedContent);
      } catch (error) {
        Logger.error("Crypto", "Decryption failed, returning original text", error);
        return encryptedText;
      }
    }
    async calculateChecksum(data) {
      const str = typeof data === "string" ? data : JSON.stringify(data);
      const encoder = new TextEncoder();
      const hashBuffer = await crypto.subtle.digest("SHA-1", encoder.encode(str));
      const hashArray = Array.from(new Uint8Array(hashBuffer));
      return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
    }
  }
  const CryptoService = new CryptoServiceImpl();
  const 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);
    },
    async getEncrypted(key, def) {
      const val = this.get(key, "");
      if (!val) return def;
      return await CryptoService.decrypt(val);
    },
    async setEncrypted(key, val) {
      const encrypted = await CryptoService.encrypt(val);
      this.set(key, encrypted);
    },
    delete: (key) => {
      if (typeof GM_deleteValue !== "undefined") GM_deleteValue(key);
    }
  };
  const Utils = {
    debounce: (func, delay) => {
      let t2;
      return (...a) => {
        if (t2) clearTimeout(t2);
        t2 = 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: (text) => {
      if (typeof GM_setClipboard !== "undefined") GM_setClipboard(text);
    }
  };
  const MediaUtils = {
    extractFC2Id: (url) => url?.match(/(?:articles\/|fc2-ppv-|fc2-|^)(\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]) return { id: fc2Prefix[1], type: "fc2" };
      const fc2FromUrl = MediaUtils.extractFC2Id(url);
      if (fc2FromUrl) return { id: fc2FromUrl, type: "fc2" };
      const dateMatch = input.match(/(\d{6,8})-(\d{1,4})/);
      if (dateMatch && dateMatch[1] && dateMatch[2]) {
        return { id: `${dateMatch[1]}-${dateMatch[2]}`, type: "jav" };
      }
      const jav = input.match(/([A-Z]{2,10})-?(\d{2,8})/);
      if (jav && jav[1] && jav[2]) {
        const prefix = jav[1];
        const lowerUrl = url.toLowerCase();
        const lowerInput = input.toLowerCase();
        if (prefix === "DM" && (lowerUrl.includes("/dm") || lowerInput.includes("/dm"))) return null;
        const blacklist = JAV_PREFIX_BLACKLIST;
        if (!blacklist.includes(prefix)) {
          return { id: `${prefix}-${jav[2]}`, type: "jav" };
        }
      }
      const fc2Raw = input.match(/(?:^|[^A-Z0-9])(\d{5,10})(?:$|[^A-Z0-9])/);
      if (fc2Raw && fc2Raw[1]) return { id: fc2Raw[1], type: "fc2" };
      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, "");
    }
  };
  const 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.assign(el.style, val);
      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 sanitized = raw.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gim, "").replace(/\s*on\w+\s*=\s*("[^"]*"|'[^']*'|[^>\s]+)/gi, "").replace(/javascript:/gi, "");
          if (raw !== sanitized) {
            Logger.warn("DOM", "Sanitized unsafe innerHTML");
          }
          el.innerHTML = sanitized;
        }
      } else if (val !== null && val !== void 0 && val !== false) {
        if (key in el) el[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;
  };
  const 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;
  };
  const IdNormalizer = {
    normalize(raw) {
      if (!raw) return "";
      const trimmed = raw.trim().toUpperCase();
      const fc2Match = trimmed.match(/(?:FC2|PPV)?-?(\d{5,8})/);
      if (fc2Match) {
        return fc2Match[1] || "";
      }
      const javMatch = trimmed.match(VALIDATION.JAV_ID_REGEX);
      if (javMatch) {
        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;
    }
  };
  const 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);
    return new Promise((resolve, reject) => {
      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 {
                resolve(res.response || JSON.parse(res.responseText));
              } catch {
                resolve(res.responseText);
              }
            } else {
              resolve(res.responseText || res.response);
            }
          } else {
            reject({ status: res.status, statusText: res.statusText, response: res.responseText });
          }
        },
        onerror: (err) => reject({ status: 0, statusText: "Network Error", error: err }),
        ontimeout: () => reject({ status: 408, statusText: "Timeout" })
      });
    });
  };
  const 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>';
  const IconGear = '<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>';
  const 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>';
  const 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>';
  const IconEyeSlash = '<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>';
  const IconRotate = '<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>';
  const 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>';
  const 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>';
  const 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>';
  const 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>';
  const 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>';
  const IconTriangleExclamation = '<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>';
  const IconCircleInfo = '<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>';
  const 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>';
  const IconSliders = '<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>';
  const IconAdjustments = '<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 32l54.7 0c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48L480 448c17.7 0 32-14.3 32-32s-14.3-32-32-32L233.3 384c-12.3-28.3-40.5-48-73.3-48s-61 19.7-73.3 48L32 384c-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 48L32 176c-17.7 0-32 14.3-32 32s14.3 32 32 32l246.7 0c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48L480 240c17.7 0 32-14.3 32-32s-14.3-32-32-32l-54.7 0c-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 48L32 64C14.3 64 0 78.3 0 96s14.3 32 32 32l86.7 0c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48L480 128c17.7 0 32-14.3 32-32s-14.3-32-32-32z"/></svg>';
  const IconDatabase = '<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>';
  const IconFilter = '<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>';
  const IconPalette = '<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>';
  const IconClockRotateLeft = '<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>';
  const IconFileExport = '<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>';
  const IconFileImport = '<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>';
  const 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>';
  const 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>';
  const IconChevronUp = '<svg viewBox="0 0 320 512" width="1.2em" height="1.2em" fill="currentColor"><path fill="currentColor" d="M182.6 137.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8H288c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z"/></svg>';
  const IconEye = '<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>';
  const IconLink = '<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>';
  const 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>';
  const 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>';
  const 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>';
  const IconServer = '<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>';
  const IconMagnifyingGlass = '<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>';
  const IconStar = '<svg viewBox="0 0 576 512" width="1.2em" height="1.2em" fill="currentColor"><path fill="currentColor" d="M316.9 18L256 126.3L195.1 18c-11.7-20.7-41.5-20.7-53.2 0l-57.8 102.3l-114.7 16c-23.7 3.3-33.2 32.4-16 49.3L42 278.4l-22.1 128.8c-4 23.3 20.6 41.2 41.6 30.2L160 374l98.5 63.3c20.9 11 45.6-7 41.6-30.2L278 278.4l88.7-92.8c17.2-16.9 7.7-46-16-49.3l-114.7-16L178.2 18c-11.7-20.7-41.5-20.7-53.2 0z"/></svg>';
  const IconHouse = '<svg viewBox="0 0 576 512" width="1.2em" height="1.2em" fill="currentColor"><path fill="currentColor" d="M280.4 7.3c-1.3-1.4-3.1-2.2-5-2.2s-3.7.8-5 2.2L34.1 231.2C22.6 242.7 16 258.1 16 274v142.1c0 35.3 28.7 64 64 64h112v-128c0-17.7 14.3-32 32-32h64c17.7 0 32 14.3 32 32v128h112c35.3 0 64-28.7 64-64V274c0-15.9-6.6-31.3-18.1-42.8L305.4 7.3z"/></svg>';
  const IconPlay = '<svg viewBox="0 0 384 512" width="1.2em" height="1.2em" fill="currentColor"><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg>';
  const IconPause = '<svg viewBox="0 0 320 512" width="1.2em" height="1.2em" fill="currentColor"><path d="M48 64C21.5 64 0 85.5 0 112V400c0 26.5 21.5 48 48 48H80c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48H48zm192 0c-26.5 0-48 21.5-48 48V400c0 26.5 21.5 48 48 48h32c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48H240z"/></svg>';
  const IconImageSearch = '<svg viewBox="0 0 512 512" width="1.2em" height="1.2em" fill="currentColor"><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.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 208zM184 96c-13.3 0-24 10.7-24 24v91L114.7 175c-7.2-7.2-18.7-7.2-25.9 0s-7.2 18.7 0 25.9L151.1 263c7.2 7.2 18.7 7.2 25.9 0L240 201c7.2-7.2 7.2-18.7 0-25.9s-18.7-7.2-25.9 0L152 237.1V120c0-13.3-10.7-24-24-24z"/></svg>';
  const IconStarRegular = '<svg viewBox="0 0 576 512" width="1.2em" height="1.2em" fill="currentColor"><path fill="currentColor" d="M287.9 0c9.2 0 17.6 5.2 21.6 13.5l68.6 141.3 153.2 22.6c9 1.3 16.5 7.6 19.3 16.3s.5 18.1-5.9 24.5L433.6 328.4l26.2 155.6c1.5 9-2.2 18.1-9.7 23.5s-17.3 6-25.3 1.7l-137-73.2L151 509.1c-8.1 4.3-17.9 3.7-25.3-1.7s-11.2-14.5-9.7-23.5l26.2-155.6L31.1 218.2c-6.5-6.4-8.7-15.9-5.9-24.5s10.3-14.9 19.3-16.3l153.2-22.6 68.6-141.3C270.3 5.2 278.7 0 287.9 0zm0 79L235.4 187.2c-3.5 7.1-10.2 12.1-18.1 13.3L99 217.9 184.9 303c5.5 5.5 8.1 13.3 6.8 21.1l-20.3 120.3L290 380c7.1-3.8 15.6-3.8 22.8 0l118.6 64.4-20.3-120.3c-1.3-7.8 1.3-15.6 6.8-21.1l85.9-85.1-118.3-17.4c-7.9-1.2-14.6-6.2-18.1-13.3L287.9 79z"/></svg>';
  class FC2Database extends Dexie {
    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",
        itemDetails: "&id, lastAccessed, folder"
      });
      this.version(7).stores({
        itemDetails: "&id, lastAccessed, folder"
      }).upgrade(() => {
      });
    }
  }
  class QueryCache {
    constructor() {
      this.cache = new Map();
      this.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;
    }
  }
  class MemoryCache {
    constructor() {
      this.cache = new Map();
    }
    get(id, expirationMs) {
      const item = this.cache.get(id);
      if (item && Date.now() - item.timestamp < expirationMs) return item.value;
      if (item) this.cache.delete(id);
      return null;
    }
    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);
    }
  }
  const log$B = Logger.scope("Database");
  class DataService {
    constructor() {
      this.qCache = new QueryCache();
      this.mCache = new MemoryCache();
      this.db = new FC2Database();
    }
    async onInit() {
      log$B.debug("Opening Dexie database");
      await this.db.open();
      log$B.info("Database opened");
    }
    get history() {
      const db2 = this.db;
      const qCache = this.qCache;
      return {
        table: db2.history,
        async add(id, status = "watched") {
          const now = ( new Date()).toISOString();
          const item = {
            id: String(id),
            timestamp: Date.now(),
            status,
            updated_at: now,
            is_deleted: 0,
            sync_dirty: 1
          };
          await db2.history.put(item);
          qCache.invalidate();
          log$B.trace(`History added: ${id} as ${status}`);
        },
        async getAll() {
          const cached = qCache.get("all");
          if (cached) return cached;
          const items = await db2.history.where("is_deleted").equals(0).toArray();
          qCache.set("all", items);
          return items;
        },
        async remove(id) {
          const now = ( new Date()).toISOString();
          await db2.history.update(String(id), { is_deleted: 1, updated_at: now, sync_dirty: 1 });
          qCache.invalidate();
        },
        async clear() {
          await db2.history.clear();
          qCache.invalidate();
        },
        async markDirty(id) {
          const now = ( new Date()).toISOString();
          await db2.history.update(String(id), { sync_dirty: 1, updated_at: now });
          qCache.invalidate();
        }
      };
    }
    get cache() {
      const db2 = this.db;
      const mCache = this.mCache;
      return {
        async get(id) {
          const exp = CACHE.MEMORY_EXPIRATION_MS;
          const memValue = mCache.get(id, exp);
          if (memValue !== null) return memValue;
          const row = await db2.cache.get(id);
          if (row && Date.now() - row.timestamp < exp) {
            mCache.set(id, row.value);
            return row.value;
          }
          return null;
        },
        async set(id, value) {
          await db2.cache.put({ id: String(id), value, timestamp: Date.now() });
          mCache.set(id, value);
        },
        async delete(id) {
          await db2.cache.delete(String(id));
          mCache.delete(id);
        },
        async clear() {
          await db2.cache.clear();
          mCache.clear();
        },
        async getAll() {
          return await db2.cache.toArray();
        }
      };
    }
    async runGC() {
      Logger.group("Database", "Garbage collection");
      const now = Date.now(), cacheExp = CACHE.EXPIRATION_MS;
      await this.db.cache.where("timestamp").below(now - cacheExp).delete();
      this.mCache.clear();
      Logger.groupEnd();
    }
    get details() {
      const db2 = this.db;
      return {
        async set(id, data) {
          const nid = String(id);
          const existing = await db2.itemDetails.get(nid);
          await db2.itemDetails.put({
            id: nid,
            lastAccessed: Date.now(),
            updated_at: ( new Date()).toISOString(),
            ...existing,
            ...data
          });
        },
        async get(id) {
          const item = await db2.itemDetails.get(String(id));
          if (item) {
            await db2.itemDetails.update(String(id), { lastAccessed: Date.now() });
          }
          return item;
        },
        async getAll() {
          return await db2.itemDetails.toArray();
        },
        async remove(id) {
          await db2.itemDetails.delete(String(id));
        },
        async clear() {
          await db2.itemDetails.clear();
        },
        async updateFolder(id, folder) {
          await db2.itemDetails.update(String(id), {
            folder,
            updated_at: ( new Date()).toISOString()
          });
        },
        async batchUpdate(ids, data) {
          const now = ( new Date()).toISOString();
          await db2.transaction("rw", db2.itemDetails, async () => {
            for (const id of ids) {
              await db2.itemDetails.update(String(id), {
                ...data,
                updated_at: now
              });
            }
          });
        },
        async batchDelete(ids) {
          await db2.itemDetails.bulkDelete(ids.map((id) => String(id)));
        }
      };
    }
  }
  const Repository = AppContainer.register("data-service", new DataService());
  Repository.cache;
  Repository.db;
  const log$A = Logger.scope("Messaging");
  var MessageType = ((MessageType2) => {
    MessageType2["SETTING_UPDATE"] = "SETTING_UPDATE";
    MessageType2["HISTORY_UPDATE"] = "HISTORY_UPDATE";
    MessageType2["UI_REFRESH"] = "UI_REFRESH";
    return MessageType2;
  })(MessageType || {});
  class MessagingServiceImplementation {
    constructor() {
      this.CHANNEL_NAME = SYSTEM_KEYS.MESSAGING_CHANNEL;
      this.tabId = Math.random().toString(36).substring(2, 11);
      this.channel = new BroadcastChannel(this.CHANNEL_NAME);
      this.listeners = new Set();
    }
    onInit() {
      this.channel.onmessage = (event) => {
        const msg = event.data;
        if (msg.sourceTabId === this.tabId) return;
        log$A.debug(`Received ${msg.type} from other tab`);
        this.listeners.forEach((l) => l(msg));
      };
    }
    broadcast(type, payload) {
      const msg = {
        type,
        payload,
        sourceTabId: this.tabId
      };
      this.channel.postMessage(msg);
      log$A.debug(`Broadcasted ${type}`);
    }
    onMessage(handler) {
      this.listeners.add(handler);
      return () => this.listeners.delete(handler);
    }
    destroy() {
      this.listeners.clear();
      this.channel.close();
    }
  }
  const MessagingService = AppContainer.register("messaging-service", new MessagingServiceImplementation());
  const log$z = Logger.scope("History");
  class HistoryServiceImplementation {
    constructor() {
      this.CACHE_SIZE = 5e3;
      this.historyCache = new Map();
    }
    getHistorySnapshot() {
      return this.historyCache;
    }
    onBootstrap() {
      log$z.debug("Warming up cache");
      this.load().catch((e) => log$z.error("Failed to load history cache", e));
      MessagingService.onMessage((msg) => {
        if (msg.type === MessageType.HISTORY_UPDATE) {
          const { action, id, status } = msg.payload;
          if (action === "add" && status) this.add(id, status, true);
          else if (action === "remove") this.remove(id, true);
          else if (action === "clear") this.clear(true);
        }
      });
    }
    touch(key, value) {
      if (this.historyCache.has(key)) this.historyCache.delete(key);
      this.historyCache.set(key, value);
      if (this.historyCache.size > this.CACHE_SIZE) {
        const firstKey = this.historyCache.keys().next().value;
        if (firstKey) this.historyCache.delete(firstKey);
      }
    }
    getNormalizedId(id) {
      return IdNormalizer.normalize(id);
    }
    getCache() {
      return new Map(this.historyCache);
    }
    async load() {
      Logger.time("HistoryService.load");
      if (!State.proxy.enableHistory) {
        this.historyCache.clear();
        return;
      }
      try {
        let items = [];
        try {
          items = await Repository.history.table.orderBy("timestamp").reverse().limit(this.CACHE_SIZE).toArray();
        } catch (idxErr) {
          log$z.warn("Index scan failed, falling back to full scan", idxErr);
          items = await Repository.history.table.toArray();
          items.sort((a, b) => b.timestamp - a.timestamp);
          if (items.length > this.CACHE_SIZE) items = items.slice(0, this.CACHE_SIZE);
        }
        this.historyCache.clear();
        for (let i = items.length - 1; i >= 0; i--) {
          const item = items[i];
          if (item) this.historyCache.set(this.getNormalizedId(item.id), item.status || "watched");
        }
        CoreEvents.emit(AppEvents.HISTORY_LOADED, items.length);
        log$z.info(`Loaded ${items.length} recent items`);
      } catch (e) {
        log$z.error("Failed to load history", e);
        this.historyCache.clear();
      }
      Logger.timeEnd("HistoryService.load");
    }
    async add(id, status = "watched", remote = false) {
      if (!State.proxy.enableHistory || !id) return;
      const idStr = this.getNormalizedId(String(id));
      if (this.historyCache.get(idStr) === status) {
        this.touch(idStr, status);
        if (!remote) return;
      }
      this.touch(idStr, status);
      if (!remote) {
        await Repository.history.add(idStr, status);
        MessagingService.broadcast(MessageType.HISTORY_UPDATE, { action: "add", id: idStr, status });
      }
      CoreEvents.emit(AppEvents.HISTORY_ADDED, { id: idStr, status });
      log$z.debug(`${remote ? "Remote" : "Local"} added: ${idStr} as ${status}`);
      if (!remote) CoreEvents.emit(AppEvents.HISTORY_CHANGED, {});
    }
    async remove(id, remote = false) {
      if (!State.proxy.enableHistory || !id) return;
      const idStr = this.getNormalizedId(String(id));
      const prevStatus = this.historyCache.get(idStr);
      this.historyCache.delete(idStr);
      if (!remote) {
        await Repository.history.remove(idStr);
        MessagingService.broadcast(MessageType.HISTORY_UPDATE, { action: "remove", id: idStr });
      }
      CoreEvents.emit(AppEvents.HISTORY_REMOVED, { id: idStr, prevStatus });
      log$z.debug(`${remote ? "Remote" : "Local"} removed: ${idStr}`);
      if (!remote) CoreEvents.emit(AppEvents.HISTORY_CHANGED, {});
    }
    async clear(remote = false) {
      const count = this.historyCache.size;
      this.historyCache.clear();
      if (!remote) {
        await Repository.history.clear();
        MessagingService.broadcast(MessageType.HISTORY_UPDATE, { action: "clear" });
      }
      CoreEvents.emit(AppEvents.HISTORY_CLEARED, count);
      log$z.warn(`${remote ? "Remote" : "Local"} cleared ${count} items`);
      if (!remote) CoreEvents.emit(AppEvents.HISTORY_CHANGED, {});
    }
    has(id, status) {
      if (!State.proxy.enableHistory) return false;
      const idStr = this.getNormalizedId(String(id));
      if (status) return this.historyCache.get(idStr) === status;
      return this.historyCache.has(idStr) && this.historyCache.get(idStr) !== "blocked";
    }
    getStatus(id) {
      const idStr = this.getNormalizedId(String(id));
      return this.historyCache.get(idStr) || null;
    }
  }
  const HistoryService = AppContainer.register("history-service", new HistoryServiceImplementation());
  const translations = {
    zh: {
      managementCenter: "管理中心",
      settingsTitle: SCRIPT_INFO.NAME,
      tabSettings: "基本设置",
      tabStatistics: "数据概览",
      tabData: "同步与导出",
      tabCollection: "我的收藏",
      tabDashboard: "运行状态",
      tabAbout: "关于",
      tabDebug: "运行日志",
      tabDmca: "免责声明",
      dashCollStatus: "收藏统计",
      dashCollCountLabel: "已收藏条目",
      dashEnterColl: "打开收藏库",
      dashSyncTitle: "同步连接",
      dashSyncStatusDetecting: "检测中...",
      dashSyncHeartbeatWait: "等待响应...",
      dashSyncConsole: "同步日志",
      dashHealthTitle: "数据维护",
      dashCacheSizeLabel: "缓存占用",
      dashRunRepair: "自动修复损坏封面",
      dashViewReport: "查看结果",
      dashRepairConfirm: "确认开始修复失效封面?系统将尝试从备用源重新抓取图片。",
      dashSyncModeNone: "同步未开启",
      dashSyncModeWebDAV: "WebDAV 已连接",
      dashSyncModeSupabase: "Supabase 已连接",
      dashSyncSuggestWebDAV: "推荐开启 WebDAV 同步以防止数据丢失",
      groupFilters: "内容过滤",
      optionHideNoMagnet: "隐藏无磁力资源",
      optionHideCensored: "过滤有码内容",
      optionHideViewed: "过滤已看内容",
      optionHideBlocked: "隐藏黑名单内容",
      groupAppearance: "界面与交互",
      groupExternalPortals: "外部传送门",
      labelPreviewMode: "预览功能",
      previewModeStatic: "静态封面",
      previewModeHover: "悬停/点击播放预览",
      labelGridColumns: "网格排版",
      labelGridAuto: "自适应宽度",
      labelDefault: "默认",
      labelLanguage: "显示语言",
      langAuto: "跟随浏览器",
      langZh: "简体中文",
      langEn: "English",
      groupDataHistory: "功能清单",
      optionEnableHistory: "开启足迹追踪",
      optionLoadExtraPreviews: "自动加载详情页视频截图",
      optionEnableQuickBar: "显示右下角悬浮球",
      optionShowViewedBtn: "显示卡片已阅开关",
      optionShowIdBadge: "显示番号复制按钮",
      optionEnableMagnets: "开启磁力资源聚合",
      optionEnableExternalLinks: "开启外部站点跳转",
      optionEnableActressName: "显示演职人员姓名",
      optionReplaceFc2Covers: "FC2PPVDB自动补全高清封面",
      labelCacheManagement: "缓存管理",
      btnClearCache: "清理磁力缓存",
      labelHistoryManagement: "历史管理",
      btnClearHistory: "清空所有历史记录",
      btnSaveAndApply: "保存并刷新",
      alertSettingsSaved: "设置已保存",
      alertCacheCleared: "磁力缓存已清理",
      alertHistoryCleared: "所有历史记录已抹除",
      alertMarkedWanted: "已加入收藏",
      alertMarkedBlocked: "已加入黑名单",
      menuOpenSettings: "⚙️ 脚本配置",
      tooltipCopyMagnet: "复制磁力链接",
      tooltipCopied: "已复制",
      tooltipLoading: "正在搜索...",
      extraPreviewTitle: "画廊预览",
      alertNoPreview: "未找到预览图",
      alertNoExternalLinks: "未找到跳转链接",
      labelExternalLinks: "外部传送门",
      groupDataManagement: "备份与恢复",
      btnExportData: "导出所有数据",
      btnImportData: "从备份恢复",
      btnResetDatabase: "重置所有设置",
      alertExportSuccess: "备份文件已开始下载",
      alertImportSuccess: "数据恢复成功,即将重新载入",
      alertImportError: "恢复失败:无效的备份文件",
      tooltipMarkAsViewed: "标为已阅",
      tooltipMarkAsUnviewed: "标为未阅",
      tooltipMarkAsWanted: "加入收藏",
      tooltipMarkAsUnwanted: "移出收藏",
      tooltipMarkAsBlocked: "屏蔽此条目",
      tooltipMarkAsUnblocked: "解除屏蔽",
      confirmResetDatabase: "警告:此操作将清空所有本地数据和配置。确定重置?",
      alertDatabaseReset: "重置完成",
      groupWebDAV: "WebDAV 同步",
      labelWebDAVUrl: "服务器地址",
      labelWebDAVUser: "账户",
      labelWebDAVPass: "密码/Token",
      labelWebDAVPath: "备份文件名",
      btnWebDAVTest: "测试连接",
      btnWebDAVSync: "立即同步",
      btnForceSync: "强制重新同步",
      alertWebDAVSuccess: "连接成功",
      alertWebDAVError: "连接失败,请检查配置",
      alertWebDAVSyncSuccess: "云同步已完成",
      alertWebDAVSyncError: "同步失败:",
      syncStatus: "连接状态",
      labelSyncMode: "同步模式",
      syncModeNone: "关闭",
      syncModeSupabase: "Supabase 云端 (高频)",
      syncModeWebDAV: "WebDAV 协议 (推荐)",
      labelLastSync: "上次同步时间",
      labelNever: "从未同步",
      labelSyncing: "同步中...",
      alertAlreadyUpToDate: "数据已同步到最新",
      alertSyncConflict: "发现云端数据有更新",
      alertSyncLocked: "同步任务已在其他标签页运行中",
      alertSyncLockActive: "其他标签页正在更新,请稍候",
      labelSyncInterval: "自动同步频率",
      syncInterval0: "实时 (较高占用)",
      syncInterval2: "每 2 分钟",
      syncInterval5: "每 5 分钟 (推荐)",
      syncInterval10: "每 10 分钟",
      syncInterval30: "每 30 分钟",
      syncIntervalManual: "仅手动触发",
      labelConflictTitle: "数据版本冲突",
      labelConflictDesc: "云端文件更新,请选择处理方式:",
      btnMergeSync: "双向合并",
      btnForceLocal: "覆盖本地数据",
      confirmOverwriteLocal: "警告:由于版本冲突,此操作将导致本地改动丢失。确定执行?",
      labelAdvancedConfig: "开发者设置",
      labelAuthEmail: "邮箱",
      labelAuthPass: "密码",
      btnConnectAndSync: "登录并同步",
      btnForcePull: "强制云端恢复",
      btnPullSync: "仅强制拉取云端数据",
      btnLogout: "注销",
      alertLoginRequired: "请填写登录信息",
      alertSbUrlRequired: "缺少 Supabase 配置信息",
      alertSyncAccountConnected: "账号连接成功",
      alertPushAllQuery: "确定将本地数据全量推送到云端?",
      alertPullAllQuery: "确定从云端覆盖本地数据?这将丢弃所有本地未同步的改动。",
      statusOn: "开启",
      statusOff: "关闭",
      labelSupabaseSync: "Supabase 同步",
      labelSupabaseUrl: "服务器地址",
      labelSupabaseKey: "密钥 (anon key)",
      labelUser: "当前用户",
      labelNotLoggedIn: "未登录",
      labelConfigError: "配置错误",
      aboutDescription: "为 FC2PPVDB 等站点提供磁力评分聚合、高清封面替换、画廊模式及历史记录同步。",
      aboutHelpTitle: "主要功能",
      aboutHelpContent: "实时显示 Sukebei / 0cili 搜索结果,并自动过滤失效条目。\n自动补全高清封面,详情页支持全屏画廊及键盘操作。\n支持 WebDAV 或 Supabase 协议,安全存储多设备浏览历史。\n右下角悬浮球、快速复制番号、外部站点一键直达。",
      aboutLinks: "相关链接",
      aboutVersion: "当前版本",
      navExtraPreviews: "查看详情",
      labelTechnicalLogs: "系统日志",
      btnCopy: "复制内容",
      btnCopyAll: "复制全部",
      btnSelectAll: "全选",
      btnDeselectAll: "取消全选",
      btnClearLogs: "清空日志",
      alertLogsCopied: "日志已复制到剪贴板",
      labelLogFilters: "日志过滤",
      tooltipClickToCopy: "复制条目",
      btnCancel: "取消",
      btnSave: "确认保存",
      btnMoreOptions: "更多功能",
      btnClose: "退出",
      btnBackToTop: "回顶部",
      labelDebugMode: "调试模式",
      statusDebugOn: "开发模式",
      statusDebugOff: "常规模式",
      alertDebugOn: "调试模式已激活",
      alertDebugOff: "已回到常规模式",
      btnCopyEnv: "导出诊断信息",
      alertEnvCopied: "诊断信息已复制",
      groupExternalImport: "数据导入/导出",
      alertMarkedViewed: "已标记为看过",
      tooltipCopyId: "复制番号",
      verifyCF: "手动验证 Cloudflare",
      dmcaContent: "本脚本仅为本地增强型工具,<b>不托管或存储任何视频或图片文件</b>。所有索引内容均来自第三方公开网站。使用者需对使用行为涉及的法律风险自行负责。如有侵权内容展示,请联系该内容的来源网站发起移除。",
      confirmReloadSettings: "设置已更改,需刷新页面生效。是否立即刷新?",
      errorWebDAVUrl: "服务器地址必须以 http:// 或 https:// 开头",
      btnPushPull: "手动同步 (上传/下载)",
      btnLogData: "查看详情",
      labelLogData: "数据日志",
      labelDisclaimer: "声明条款",
      labelGreasyFork: "Greasy Fork 主页",
      labelHidden: "隐藏",
      labelVisible: "显示",
      labelLoading: "加载中...",
      alertLoggedOut: "已成功退出",
      alertUserIdMissing: "同步失败:无法识别用户身份,请尝试重新登录",
      mainPreview: "功能演示",
      gallerySearch: "以图搜图",
      gallerySlideshow: "开启幻灯片播放",
      searchPlaceholder: "在当前列表中搜索...",
      collBatchMode: "批量编辑",
      collSelected: "已选 {count} 项",
      collSortNewest: "按时间 (从新到旧)",
      collSortOldest: "按时间 (从旧到新)",
      collSortTitle: "按标题 (A-Z)",
      collSortSite: "按来源网站",
      collFolderAll: "全部目录",
      collSiteAll: "所有站点",
      collCheckHealth: "扫描失效链接",
      collShowDupes: "查找重复项",
      collBatchMerge: "数据合并",
      collBatchMove: "移动至文件夹...",
      collBatchRemove: "批量移除",
      collBatchRemoveSuccess: "所选项目已移除",
      collImportJson: "外部备份导入",
      collExportJson: "备份数据到本地",
      collUndo: "撤销操作",
      collMerging: "合并处理中...",
      collMoveTarget: "选择目标目录 (现有: {folders})",
      collMoveSuccess: "已成功移动 {count} 个条目",
      collRemoveSuccess: "已成功移除",
      collTotal: "总记录数",
      collShown: "当前可见",
      labelError: "发生错误",
      confirmDelete: "确定物理删除?此操作不可撤销。",
      confirmDeleteSelected: "确定彻底清理选中的 {count} 个条目?",
      confirmForceSync: "确定执行强制覆盖同步?系统将不对比差异。",
      confirmDestructiveAction: "注意:此操作具有破坏性,执行后数据无法找回。",
      folderWanted: "收藏夹",
      folderViewed: "已阅历史",
      folderFollow: "订阅列表",
      labelAll: "全部条目",
      titleEditMetadata: "校对元数据",
      labelRating: "打分",
      labelNotes: "备注信息",
      labelUserTags: "标签",
      placeholderAddTag: "输入标签并按回车...",
      alertMetadataSaved: "设置保存成功",
      collNewFolder: "新建文件夹...",
      collRenameFolder: "重命名",
      collDeleteFolder: "删除当前文件夹",
      promptFolderName: "请输入文件夹名称",
      promptNewFolderName: "请输入新的名称",
      confirmDeleteFolder: '确定删除文件夹 "{folder}"?条目将自动回落到全部列表。',
      alertFolderRenamed: "重命名已完成",
      collStatsRated: "已评分",
      collStatsAvg: "平均",
      collStatsWithNotes: "有备注",
      collStatsWithTags: "有标签",
      collSortFolder: "按文件夹",
      collHealthResult: "检查完成:扫描 {checked} 项,修复 {repaired} 项",
      collImportSuccess: "成功导入 {count} 个条目",
      collImportPartial: "已导入 {count} 个条目,{errors} 个失败"
    },
    en: {
      managementCenter: "Management Center",
      settingsTitle: SCRIPT_INFO.NAME,
      tabSettings: "Preferences",
      tabStatistics: "Analytics",
      tabData: "Data & Sync",
      tabCollection: "Collection",
      tabDashboard: "Overview",
      tabAbout: "About",
      tabDebug: "Technical Logs",
      tabDmca: "Disclaimer",
      dashCollStatus: "Collection Status",
      dashCollCountLabel: "Items Collected",
      dashEnterColl: "Enter Collection",
      dashSyncTitle: "System Connectivity",
      dashSyncStatusDetecting: "Detecting...",
      dashSyncHeartbeatWait: "Waiting for heartbeat...",
      dashSyncConsole: "Sync Console",
      dashHealthTitle: "Lifecycle & Self-Healing",
      dashCacheSizeLabel: "Local Cache Size (items)",
      dashRunRepair: "Run Repair",
      dashViewReport: "View Report",
      dashRepairConfirm: "Run global self-healing? System will scan failed covers and try fallback sources.",
      dashSyncModeNone: "Sync Disabled",
      dashSyncModeWebDAV: "WebDAV Exported",
      dashSyncModeSupabase: "Supabase Connected",
      dashSyncSuggestWebDAV: "Recommended: Enable WebDAV in Sync settings",
      groupFilters: "Filters",
      optionHideNoMagnet: "Filter No-Magnet",
      optionHideCensored: "Filter Censored",
      optionHideViewed: "Filter Viewed",
      optionHideBlocked: "Filter Blocked",
      groupAppearance: "Appearance",
      groupExternalPortals: "External Portals (Custom)",
      labelPreviewMode: "Preview Mode",
      previewModeStatic: "Static Cover",
      previewModeHover: "Hover/Click Play",
      labelGridColumns: "Grid Columns",
      labelGridAuto: "Auto Fit (Recommended)",
      labelDefault: "Default",
      labelLanguage: "Language",
      langAuto: "Auto",
      langZh: "Chinese",
      langEn: "English",
      groupDataHistory: "Enhancements",
      optionEnableHistory: "Track History",
      optionLoadExtraPreviews: "Preload Gallery",
      optionEnableQuickBar: "Show Quick FAB",
      optionShowViewedBtn: "Show Card Viewed Button",
      optionShowIdBadge: "Show Card ID Badge",
      optionEnableMagnets: "Enable Magnet Search",
      optionEnableExternalLinks: "Show External Links",
      optionEnableActressName: "Show Actress Name",
      optionReplaceFc2Covers: "Replace FC2PPVDB Covers",
      labelCacheManagement: "Storage",
      btnClearCache: "Clear Magnet Cache",
      labelHistoryManagement: "History",
      btnClearHistory: "Clear History",
      btnSaveAndApply: "Save Changes",
      alertSettingsSaved: "Settings saved",
      alertCacheCleared: "Cache cleared",
      alertHistoryCleared: "History cleared",
      alertMarkedWanted: "Added to Wanted",
      alertMarkedBlocked: "Blocked Works",
      menuOpenSettings: "⚙️ Settings",
      tooltipCopyMagnet: "Magnet",
      tooltipCopied: "Copied",
      tooltipLoading: "Searching...",
      extraPreviewTitle: "Gallery",
      alertNoPreview: "No Previews",
      alertNoExternalLinks: "No external links found",
      labelExternalLinks: "Links",
      groupDataManagement: "Backup",
      btnExportData: "Export Backup",
      btnImportData: "Restore Backup",
      btnResetDatabase: "Reset All",
      alertExportSuccess: "Backup started",
      alertImportSuccess: "Restore successful, refreshing...",
      alertImportError: "Restore failed: Invalid file",
      tooltipMarkAsViewed: "Mark viewed",
      tooltipMarkAsUnviewed: "Unmark viewed",
      tooltipMarkAsWanted: "Add to Wanted",
      tooltipMarkAsUnwanted: "Remove from Wanted",
      tooltipMarkAsBlocked: "Block this",
      tooltipMarkAsUnblocked: "Unblock this",
      confirmResetDatabase: "Danger: Delete ALL data and settings?",
      alertDatabaseReset: "Reset complete",
      groupWebDAV: "WebDAV Sync",
      labelWebDAVUrl: "Server URL",
      labelWebDAVUser: "Username",
      labelWebDAVPass: "Password/Token",
      labelWebDAVPath: "Filename",
      btnWebDAVTest: "Test",
      btnWebDAVSync: "Sync Now",
      btnForceSync: "Force Full Sync",
      alertWebDAVSuccess: "Connected",
      alertWebDAVError: "Connection failed",
      alertWebDAVSyncSuccess: "Sync Complete",
      alertWebDAVSyncError: "Sync Failed: ",
      syncStatus: "Status",
      labelSyncMode: "Strategy",
      syncModeNone: "Off",
      syncModeSupabase: "Supabase (Adv)",
      syncModeWebDAV: "WebDAV (Rec)",
      labelLastSync: "Last Sync",
      labelNever: "Never",
      labelSyncing: "Syncing...",
      alertAlreadyUpToDate: "Already up to date",
      alertSyncConflict: "Remote Update Detected",
      alertSyncLocked: "Sync already in progress in another tab",
      alertSyncLockActive: "Sync in progress in another tab...",
      labelSyncInterval: "Auto-Sync Interval",
      syncInterval0: "Real-time (No limit)",
      syncInterval2: "2 minutes",
      syncInterval5: "5 minutes (Recommended)",
      syncInterval10: "10 minutes",
      syncInterval30: "30 minutes",
      syncIntervalManual: "Manual only",
      labelConflictTitle: "Conflict",
      labelConflictDesc: "Remote data is newer. Strategy:",
      btnMergeSync: "Merge",
      btnForceLocal: "Overwrite",
      confirmOverwriteLocal: "Overwrite local history from remote?",
      labelAdvancedConfig: "Dev Config",
      labelAuthEmail: "Email",
      labelAuthPass: "Password",
      btnConnectAndSync: "Login",
      btnForcePull: "Force Pull (Restore)",
      btnPullSync: "Force Pull Sync",
      btnLogout: "Logout",
      alertLoginRequired: "Credentials missing",
      alertSbUrlRequired: "URL missing",
      alertSyncAccountConnected: "Connected",
      alertPushAllQuery: "Push all local data?",
      alertPullAllQuery: "Restore all data from cloud? This will overwrite local conflicts.",
      statusOn: "ON",
      statusOff: "OFF",
      labelSupabaseSync: "Supabase",
      labelSupabaseUrl: "Supabase URL",
      labelSupabaseKey: "Supabase Key",
      labelUser: "User",
      labelNotLoggedIn: "Not Configured",
      labelConfigError: "Config Error",
      aboutDescription: "Inject magnet links, HD covers, gallery mode, and history sync for FC2PPVDB, Supjav, and more.",
      aboutHelpTitle: "Features",
      aboutHelpContent: "• <b>Magnet Links</b>: Aggregated Sukebei / 0cili search with smart filtering.\n• <b>Visuals</b>: Auto-replace HD covers, gallery mode with keyboard nav.\n• <b>Cloud Sync</b>: Sync history via WebDAV / Supabase across devices.\n• <b>UX</b>: FAB, instant ID copy, external link shortcuts.",
      aboutLinks: "Links",
      aboutVersion: "Version",
      navExtraPreviews: "Gallery",
      labelTechnicalLogs: "Technical Logs",
      btnCopy: "Copy",
      btnCopyAll: "Copy All",
      btnSelectAll: "Select All",
      btnDeselectAll: "Deselect All",
      btnClearLogs: "Clear",
      alertLogsCopied: "Logs copied to clipboard",
      labelLogFilters: "Filters",
      tooltipClickToCopy: "Copy",
      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",
      alertEnvCopied: "Diagnostic info copied",
      groupExternalImport: "External Import",
      alertMarkedViewed: "Marked",
      tooltipCopyId: "Copy ID",
      verifyCF: "Verify CF",
      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 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.",
      confirmReloadSettings: "Some settings require a reload to take effect. Reload now?",
      errorWebDAVUrl: "URL must start with http:// or https://",
      btnPushPull: "Push/Pull",
      btnLogData: "Data",
      labelLogData: "Log Data",
      labelDisclaimer: "Disclaimer",
      labelGreasyFork: "Greasy Fork",
      labelHidden: "Hidden",
      labelVisible: "Visible",
      labelLoading: "Loading...",
      alertLoggedOut: "Logged out",
      alertUserIdMissing: "Sync failed: User ID missing. Please login again.",
      mainPreview: "Feature Preview",
      gallerySearch: "Search Image",
      gallerySlideshow: "Slideshow",
      searchPlaceholder: "Search in collection...",
      collBatchMode: "Batch Mode",
      collSelected: "{count} Selected",
      collSortNewest: "Date (Newest)",
      collSortOldest: "Date (Oldest)",
      collSortTitle: "Title (A-Z)",
      collSortSite: "Site",
      collFolderAll: "All Folders",
      collSiteAll: "All Sites",
      collCheckHealth: "Check Health",
      collShowDupes: "Show Dupes",
      collBatchMerge: "Merge",
      collBatchMove: "Move to...",
      collBatchRemove: "Batch Remove",
      collBatchRemoveSuccess: "Batch removal completed",
      collImportJson: "Import JSON",
      collExportJson: "Export JSON",
      collUndo: "Undo",
      collMerging: "Merging...",
      collMoveTarget: "Enter folder name (Available: {folders})",
      collMoveSuccess: "Moved {count} items to {target}",
      collRemoveSuccess: "Removed from collection",
      collTotal: "Total",
      collShown: "Shown",
      labelError: "Error",
      confirmDelete: "Delete confirm?",
      confirmDeleteSelected: "Delete selected {count} items?",
      confirmForceSync: "Force full sync?",
      confirmDestructiveAction: "Warning: Irreversible action. Continue?",
      folderWanted: "Wanted",
      folderViewed: "Viewed",
      folderFollow: "Followed",
      labelAll: "All",
      titleEditMetadata: "Edit Metadata",
      labelRating: "Rating",
      labelNotes: "Notes",
      labelUserTags: "Tags",
      placeholderAddTag: "Enter tag...",
      alertMetadataSaved: "Metadata saved",
      collNewFolder: "New Folder...",
      collRenameFolder: "Rename Folder",
      collDeleteFolder: "Delete Folder",
      promptFolderName: "Enter folder name",
      promptNewFolderName: "Enter new folder name",
      confirmDeleteFolder: 'Delete folder "{folder}"? Items will remain in All.',
      alertFolderRenamed: "Folder renamed",
      collStatsRated: "Rated",
      collStatsAvg: "Avg",
      collStatsWithNotes: "With Notes",
      collStatsWithTags: "With Tags",
      collSortFolder: "By Folder",
      collHealthResult: "Health check: {checked} scanned, {repaired} repaired",
      collImportSuccess: "Imported {count} items",
      collImportPartial: "Imported {count} items, {errors} failed"
    }
  };
  const Localization = {
    _translations: translations,
t(key, params) {
      const lang = State.proxy.language === "auto" ? navigator.language.startsWith("zh") ? "zh" : "en" : State.proxy.language;
      const set = this._translations[lang] || this._translations["en"];
      let value = set?.[key] || this._translations["en"]?.[key] || key;
      if (params && typeof value === "string") {
        Object.entries(params).forEach(([k, v]) => {
          value = value.replace(new RegExp(`{${k}}`, "g"), String(v));
        });
      }
      return value;
    },
resolvePath(obj, path) {
      try {
        return path.split(".").reduce((prev, curr) => {
          if (prev && typeof prev === "object") return prev[curr];
          return void 0;
        }, obj);
      } catch {
        return null;
      }
    },
    register(lang, newTranslations) {
      if (!this._translations[lang]) {
        this._translations[lang] = {};
      }
      const tLang = this._translations[lang];
      if (tLang) this.deepMerge(tLang, 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;
    }
  };
  const t = (key, params) => Localization.t(key, params);
  const _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=");
  })();
  const UIUtils = {
    h,
    icon: (svgContent, className = "") => {
      return h("span", { className: `fc2-icon ${className}`.trim(), innerHTML: svgContent });
    },
    hasHistory: (id, status) => {
      return HistoryService.has(id, status);
    },
    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);
      }
    },
    markByStatus: (id, status, el, applyVisibility) => {
      if (!State.proxy.enableHistory) return;
      HistoryService.add(id, status);
      const c = el.closest(`.${Config.CLASSES.processedCard}`) || el;
      if (c) {
        if (status === "blocked") {
          c.classList.add(Config.CLASSES.isBlocked);
        } else if (status === "wanted") {
          c.classList.add(Config.CLASSES.isWanted);
        } else if (status === "downloaded") {
          c.classList.add(Config.CLASSES.isDownloaded);
        } else {
          c.classList.add(Config.CLASSES.isViewed);
        }
        const vBtn = c.querySelector(".btn-toggle-view");
        if (vBtn && status === "watched") vBtn.classList.add("is-viewed");
        const oc = c.classList.contains(Config.CLASSES.cardRebuilt) ? c : c.closest(`.${Config.CLASSES.cardRebuilt}`);
        if (oc) {
          applyVisibility(oc);
        }
      }
    },
    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");
      }
    },
    applyCardVisibility: (c, hasM) => {
      if (!c) return;
      const target = c.classList.contains(Config.CLASSES.cardRebuilt) ? c : c.closest(`.${Config.CLASSES.cardRebuilt}`) || c;
      const processed = target.classList.contains(Config.CLASSES.processedCard) ? target : target.querySelector(`.${Config.CLASSES.processedCard}`);
      const isSearching = processed?.dataset?.enhSearching === "true";
      const shouldHide = !_isSearchPage && State.proxy.hideNoMagnet && !hasM && !isSearching;
      target.classList.toggle(Config.CLASSES.hideNoMagnet, shouldHide);
    },
    applyCensoredFilter: (c) => {
      if (!c) return;
      const target = c.classList.contains(Config.CLASSES.cardRebuilt) ? c : c.closest(`.${Config.CLASSES.cardRebuilt}`) || c;
      const isCensored = target.classList.contains(Config.CLASSES.isCensored) || !!target.querySelector(`.${Config.CLASSES.isCensored}`);
      const isSupjav = location.hostname.includes("supjav");
      target.classList.toggle(
        Config.CLASSES.hideCensored,
        !_isSearchPage && isSupjav && State.proxy.hideCensored && isCensored
      );
    },
    applyHistoryVisibility: (c) => {
      if (!c) return;
      const target = c.classList.contains(Config.CLASSES.cardRebuilt) ? c : c.closest(`.${Config.CLASSES.cardRebuilt}`) || c;
      const isViewed = target.classList.contains(Config.CLASSES.isViewed) || !!target.querySelector(`.${Config.CLASSES.isViewed}`);
      target.classList.toggle(Config.CLASSES.hideViewed, !_isSearchPage && State.proxy.hideViewed && isViewed);
      target.classList.toggle(
        Config.CLASSES.hideBlocked,
        !_isSearchPage && target.classList.contains(Config.CLASSES.isBlocked)
      );
    },
    applyCollectionFilter: (c) => {
      if (!c) return;
      const target = c.classList.contains(Config.CLASSES.cardRebuilt) ? c : c.closest(`.${Config.CLASSES.cardRebuilt}`) || c;
      const isWanted = target.classList.contains(Config.CLASSES.isWanted) || !!target.querySelector(`.${Config.CLASSES.isWanted}`);
      target.classList.toggle("fc2-hide-unwanted", !_isSearchPage && State.proxy.hideUnwanted && !isWanted);
    }
  };
  const tokens = `
    /* ============================================================
       DESIGN TOKENS & DESIGN SYSTEM
       ============================================================ */

    :root, :host {
        /* --- Elevation & Depth --- */
        --fc2-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
        --fc2-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
        --fc2-shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.5);
        --fc2-shadow-glow: none;

        /* --- Colors (Semantic / Catppuccin Base) --- */
        --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-border: rgba(255, 255, 255, 0.08);
        --fc2-primary: #ffffff;
        --fc2-on-primary: #000000;
        --fc2-primary-rgb: 255, 255, 255;
        --fc2-success: #34d399;
        --fc2-danger: #f87171;
        --fc2-warn: #fab387;
        --fc2-info: #89b4fa;
        --fc2-accent: #e4e4e7;
        
        /* --- Animation Curves --- */
        --fc2-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
        --fc2-ease-in: cubic-bezier(0.7, 0, 0.84, 0);
        --fc2-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
        --fc2-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
        
        /* --- Spacing Scale (8px Grid) --- */
        --fc2-space-xs: 4px;
        --fc2-space-sm: 8px;
        --fc2-space-base: 12px;
        --fc2-space-md: 16px;
        --fc2-space-lg: 24px;
        --fc2-space-xl: 32px;

        /* --- Gradients --- */
        --fc2-accent-grad: linear-gradient(135deg, #3f3f46, #18181b);
        --fc2-magnet-grad: linear-gradient(135deg, #52525b, #27272a);
        
        /* --- Layout & Shape --- */
        --fc2-radius-sm: 6px;
        --fc2-radius-md: 12px;
        --fc2-radius-lg: 16px;
        --fc2-radius-xl: 20px;
        --fc2-radius: var(--fc2-radius-lg);
        --fc2-radius-full: 9999px;
        --fc2-btn-radius: 10px;
        --fc2-blur: 0px; 
        --fc2-glass-saturate: 100%;
        --fc2-font: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
        --fc2-font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Fira Code', monospace;

        /* --- Stealth Pro (Solid Black) --- */
        --fc2-liquid-bg: #0a0a0c; /* Truly deep black base */
        --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");

        /* --- Global Component Styles --- */
        --fc2-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);

        /* Z-Index Scale */
        --fc2-z-toast: 2147483647;
        --fc2-z-gallery: 2147483645;
        --fc2-z-actionsheet: 2147483640;
        --fc2-z-fab: 2147483620;
        --fc2-z-modal: 2147483600;
        --fc2-z-settings: 2147483550;
        --fc2-z-overlay: 2147483500;
        --fc2-z-tooltip: 100000;
        --fc2-z-dropdown: 1000;
        
        /* Scrollbar Colors */
        --fc2-scrollbar-thumb: rgba(255, 255, 255, 0.08);
        --fc2-scrollbar-hover: rgba(255, 255, 255, 0.15);

        /* Aliases for settings panel (Legacy Compatibility) */
        --fc2-enh-bg: var(--fc2-liquid-bg);
        --fc2-enh-bg-secondary: rgba(18, 18, 20, 0.3);
        --fc2-enh-text: #f4f4f7;
        --fc2-enh-border: var(--fc2-liquid-border);
    }

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

    :root.fc2-light-theme {
        --fc2-bg: #f8f9fa;
        --fc2-surface: rgba(255, 255, 255, 0.8);
        --fc2-text: #1a1a1a;
        --fc2-text-dim: #52525b; /* Darkened from #71717a for better contrast */
        --fc2-border: rgba(0, 0, 0, 0.08);
        --fc2-primary: #111111;
        --fc2-on-primary: #ffffff;
        --fc2-success: #16a34a;
        --fc2-danger: #dc2626;
        --fc2-accent: #3f3f46;
        
        --fc2-accent-grad: linear-gradient(135deg, #e4e4e7, #f4f4f5);
        --fc2-magnet-grad: linear-gradient(135deg, #d4d4d8, #e4e4e7);
        
        --fc2-scrollbar-thumb: rgba(0, 0, 0, 0.15);
        --fc2-scrollbar-hover: rgba(0, 0, 0, 0.25);
        
        --fc2-shadow: 0 12px 32px rgba(0, 0, 0, 0.1);

        --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-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%);
    }
`;
  const animations = `
    /* ============================================================
       GLOBAL ANIMATIONS
       ============================================================ */

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

    /* Master Reveal Animation for Cards */
    @keyframes fc2-card-reveal {
        0% { 
            opacity: 0; 
            transform: translate3d(0, 12px, 0) scale(0.97);
            filter: blur(4px);
        }
        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 {
        0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.4); }
        70% { box-shadow: 0 0 0 10px rgba(255, 255, 255, 0); }
        100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
    }

    @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: #111;
        }
        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(20px);
            opacity: 0;
            filter: blur(5px);
        }
        60% {
            transform: scale(1.1) translateY(-5px);
            filter: blur(0);
        }
        85% {
            transform: scale(0.95) translateY(2px);
        }
        100% {
            transform: scale(1) translateY(0);
            opacity: 1;
        }
    }

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

    /* 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(30px); }
        100% { opacity: 1; transform: translateX(0); }
    }

    @keyframes fc2-slide-left-in {
        0% { opacity: 0; transform: translateX(-30px); }
        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 2px var(--fc2-primary)); }
        50% { filter: drop-shadow(0 0 10px var(--fc2-primary)); }
        100% { filter: drop-shadow(0 0 2px var(--fc2-primary)); }
    }

    /* Helper Classes */
    .pulse-once {
        animation: fc2-pulse-once 0.6s var(--fc2-ease-out);
    }
    .fc2-animate-pop {
        animation: fc2-animate-pop 0.3s var(--fc2-ease-out);
    }
    .fc2-star-burst::after {
        content: '';
        position: absolute;
        inset: -10px;
        border: 2px solid var(--fc2-primary);
        border-radius: 50%;
        animation: fc2-star-burst 0.5s ease-out forwards;
        pointer-events: none;
    }
`;
  const getBaseStyles = (C) => `
    /* ============================================================
       CSS RESET & GLOBAL OVERRIDES
       ============================================================ */

    .fc2-enh-settings-panel,
    .fc2-enh-modal-overlay,
    .enh-modal-panel,
    .fc2-action-sheet,
    .fc2-collection-grid,
    .fc2-collection-toolbar,
    .\${C.cardRebuilt},
    .\${C.processedCard},
    .fc2-fab-container {
        all: revert;
        box-sizing: border-box;
        -webkit-tap-highlight-color: transparent !important;
        -webkit-touch-callout: none !important;
        transition: background-color 0.4s var(--fc2-ease-standard), 
                    color 0.4s var(--fc2-ease-standard), 
                    border-color 0.4s var(--fc2-ease-standard);
    }

    .fc2-enh-settings-panel *:not(svg):not(path):not(circle):not(rect):not(line):not(polyline):not(polygon),
    .fc2-enh-modal-overlay *:not(svg):not(path):not(circle):not(rect):not(line):not(polyline):not(polygon),
    .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),
    .fc2-collection-grid *:not(svg):not(path):not(circle):not(rect):not(line):not(polyline):not(polygon),
    .fc2-collection-toolbar *:not(svg):not(path):not(circle):not(rect):not(line):not(polyline):not(polygon),
    .\${C.cardRebuilt} *:not(svg):not(path):not(circle):not(rect):not(line):not(polyline):not(polygon),
    .\${C.processedCard} *: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: 14px !important;
        font-weight: normal !important;
        font-style: normal !important;
        line-height: 1.5 !important;
        letter-spacing: normal !important;
        text-transform: none !important;
    }

    /* Icons Visibility Fix */
    .fc2-enh-settings-panel svg,
    .fc2-enh-settings-panel .fc2-icon {
        display: inline-block !important;
        vertical-align: middle !important;
    }

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

    .fc2-enh-settings-panel h2 { font-size: 20px !important; }
    .fc2-enh-settings-panel h3 { font-size: 16px !important; }
    .fc2-enh-settings-panel h4 { font-size: 14px !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: 14px !important;
        line-height: 1.5 !important;
    }

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

    .fc2-enh-settings-panel select,
    .fc2-enh-settings-panel .fc2-select {
        display: inline-block !important;
        padding: 6px 32px 6px 12px !important;
        background: var(--fc2-surface-float) !important;
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath fill='%23ffffff' 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 8px center !important;
        background-size: 12px !important;
        color: var(--fc2-text) !important;
        border: 1px solid var(--fc2-border) !important;
        border-radius: var(--fc2-radius-sm) !important;
        cursor: pointer !important;
        appearance: none !important;
        -webkit-appearance: none !important;
        color-scheme: dark !important;
        filter: invert(0) !important;
        transition: all 0.2s var(--fc2-ease-standard);
    }

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

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

    .fc2-enh-settings-panel select option {
        padding: 6px 12px !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-text) !important;
    }

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

    /* Global Scrollbar */
    ::-webkit-scrollbar {
        width: 6px;
        height: 6px;
    }

    ::-webkit-scrollbar-track {
        background: transparent;
    }

    ::-webkit-scrollbar-thumb {
        background: var(--fc2-scrollbar-thumb) !important;
        border-radius: 10px;
    }

    ::-webkit-scrollbar-thumb:hover {
        background: var(--fc2-scrollbar-hover) !important;
    }

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

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

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

    .fc2-ml-sm {
        margin-left: 8px !important;
    }

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

    #fc2-enh-settings-container {
        position: absolute;
        inset: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        font-family: var(--fc2-font);
    }
`;
  const base = (_C) => `
    /* ============================================================
       BASE COMPONENTS & RESET
       ============================================================ */

    /* Global Mono Penetration for industrial feel */
    .fc2-stat-value,
    .fc2-id-badge,
    .fc2-stat-label,
    .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:0.6;
        font-size:10px;
        width:1em;
        height:1em;
    }

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

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

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

    .fc2-mt-sm { margin-top:0.5rem; }
    .fc2-gap-sm { gap:8px; }
`;
  const 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 {
        display:flex;
        align-items:center;
        min-width:280px;
        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:14px;
        font-weight:500;
        backdrop-filter:blur(var(--fc2-blur));
        -webkit-backdrop-filter:blur(var(--fc2-blur));
        border:1px solid var(--fc2-border);
        opacity:0;
        pointer-events:auto;
        animation:fc2-toast-in 0.25s var(--fc2-ease-out) forwards;
    }

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

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

    .fc2-toast-content {
        flex-grow:1;
        line-height:1.4;
    }

    .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 0.2s;
        display:flex;
        align-items:center;
    }

    .fc2-toast-close:hover {
        background:rgba(255,255,255,0.1);
        color:var(--fc2-text);
    }

    .fc2-toast-progress {
        position:absolute;
        bottom:0;
        left:0;
        height:2px;
        width:100%;
        transform-origin:left;
    }

    .toast-success { border-left:4px solid var(--fc2-success); }
    .toast-success.fc2-toast-icon { color:var(--fc2-success); }
    .toast-error { border-left:4px solid var(--fc2-danger); }
    .toast-error.fc2-toast-icon { color:var(--fc2-danger); }
    .toast-warn { border-left:4px solid var(--fc2-warn); }
    .toast-warn.fc2-toast-icon { color:var(--fc2-warn); }
    .toast-info { border-left:4px 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:2px 8px;
        background:rgba(255,255,255,0.1);
        border:1px solid rgba(255,255,255,0.2);
        border-radius:4px;
        color:var(--fc2-primary);
        font-size:12px;
        font-weight:600;
        cursor:pointer;
        transition:all 0.2s;
    }

    .fc2-toast-action:hover {
        background:var(--fc2-primary);
        color:var(--fc2-on-primary);
        border-color:transparent;
    }
`;
  const fab = (_C) => `
    /* ============================================================
       ELEGANT FAB (UNIFIED)
       ============================================================ */

    .fc2-fab-container {
        position:fixed;
        bottom:20px;
        right:20px;
        z-index:var(--fc2-z-fab);
        display:flex;
        flex-direction:column;
        align-items:center;
        justify-content:flex-end;
        gap:12px;
        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:48px;
        height:48px;
        background:var(--fc2-accent-grad);
        color:var(--fc2-text);
        border:var(--fc2-liquid-border);
        border-radius:50%;
        box-shadow:var(--fc2-shadow),var(--fc2-rim-light);
        font-size:20px;
        cursor:pointer;
        transition:all 0.3s var(--fc2-ease-out);
        touch-action:none;
        -webkit-tap-highlight-color:transparent;
        will-change:transform;
        backdrop-filter:blur(12px);
    }

    .fc2-fab-trigger:hover {
        transform:scale(1.12);
        box-shadow:0 15px 35px rgba(0,0,0,0.4);
    }

    .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(20px) scale(0.8);
        pointer-events:none;
        transition:all 0.4s var(--fc2-ease-out);
        max-height:60vh;
        width:64px;
        overflow-y:auto!important;
        overflow-x:hidden;
        padding:10px;
        margin-bottom:8px;
        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:6px;
        display:block!important;
    }
    .fc2-fab-actions::-webkit-scrollbar-track {
        background:transparent;
    }
    .fc2-fab-actions::-webkit-scrollbar-thumb {
        background-color:var(--fc2-primary);
        border-radius:10px;
        border:1px solid rgba(255,255,255,0.1);
    }

    .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:44px;
        height:44px;
        background:rgba(255,255,255,0.05);
        color:var(--fc2-text-dim);
        border:1px solid rgba(255,255,255,0.08);
        border-radius:50%;
        font-size:16px;
        cursor:pointer;
        transition:all 0.25s var(--fc2-ease-out);
        -webkit-tap-highlight-color:transparent;
        z-index:2;
    }

    .fc2-fab-btn:hover {
        background:rgba(255,255,255,0.12);
        color:var(--fc2-text);
        border-color:rgba(255,255,255,0.2);
        transform:scale(1.15);
        box-shadow:0 10px 20px rgba(0,0,0,0.3);
    }

    .fc2-fab-btn.active {
        background:var(--fc2-primary);
        color:var(--fc2-on-primary);
        border-color:transparent;
        box-shadow:0 0 20px rgba(var(--fc2-primary-rgb),0.4);
    }

    .fc2-fab-btn::before {
        content:attr(data-title);
        position:absolute;
        right:52px;
        top:50%;
        visibility:hidden;
        padding:var(--fc2-space-xs) var(--fc2-space-sm);
        background:rgba(0,0,0,0.85);
        color:#fff;
        border-radius:var(--fc2-radius-sm);
        font-size:12px;
        white-space:nowrap;
        opacity:0;
        backdrop-filter:blur(4px);
        transform:translateY(-50%) translateX(5px);
        transition:all 0.2s;
        pointer-events:none;
    }

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

    .fc2-sync-dot {
        position:absolute;
        top:-2px;
        right:-2px;
        width:10px;
        height:10px;
        background:var(--fc2-text-muted);
        border:2px solid var(--fc2-bg);
        border-radius:50%;
        transition:all 0.3s;
    }

    .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); }
`;
  const card = (C) => `
    /* ============================================================
       CARD SYSTEM
       ============================================================ */

    .${C.cardRebuilt} {
        position:relative;
        background:var(--fc2-surface);
        border:1px solid var(--fc2-border);
        border-radius:var(--fc2-radius);
        animation:fc2-card-reveal 0.35s var(--fc2-ease-out) backwards!important;
        will-change: transform, opacity;
        container-type:inline-size;
        container-name:card;
        transition:none!important;
    }

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

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

    /* Critical:Hide Viewed Cards */
    body.hide-viewed .${C.processedCard}.${C.isViewed} {
        display:none!important;
    }

    body.searching .${C.cardRebuilt}:not(.search-match) {
        display:none!important;
    }

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

    .${C.cardRebuilt}.fc2-hide-unwanted {
        display:none!important;
    }

    .${C.processedCard} {
        position:relative;
        display:flex;
        flex-direction:column;
        height:100%;
        background:var(--fc2-surface);
        border:1px solid var(--fc2-border);
        border-radius:var(--fc2-radius-lg);
        overflow:visible;
        transition:transform 0.4s var(--fc2-ease-out),
            box-shadow 0.4s var(--fc2-ease-out),
            border-color 0.3s ease;
    }

    .${C.processedCard}:hover {
        transform:translateY(-8px) scale(1.02);
        border-color:rgba(var(--fc2-primary-rgb),0.5);
        box-shadow:0 20px 40px rgba(0,0,0,0.4),
            0 0 0 1px rgba(255,255,255,0.1);
        z-index:20;
    }

    .${C.processedCard}.has-active-dropdown,
    .${C.cardRebuilt}.has-active-dropdown {
        z-index:100!important;
        overflow:visible!important;
    }

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

    /* Detail Page Poster Style (Vertical) */
    .${C.processedCard}.is-detail {
        width:100%!important;
        max-width:none!important;
        height:auto!important;
    }

    .${C.processedCard}.is-detail .${C.videoPreviewContainer} {
        aspect-ratio:auto!important;
        height:auto!important;
        background:transparent!important;
    }

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

    .${C.processedCard}.is-detail .${C.infoArea} {
        padding:10px var(--fc2-space-base)!important;
        margin-top:0!important;
    }

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

    /* Minimal Card (Collection Tab) */
    .${C.processedCard}.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}.is-minimal .${C.videoPreviewContainer} {
        display:block!important;
    }

    .${C.processedCard}.is-minimal .card-top-right-controls {
        top:6px!important;
        right:6px!important;
    }

    .${C.processedCard}.is-minimal .card-top-right-controls > *:not(.btn-toggle-wanted) {
        display:none!important;
    }

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

    .${C.processedCard}.is-minimal .${C.infoArea} {
        padding:4px 10px 8px!important;
        background:transparent!important;
        border-top:none!important;
    }

    .${C.processedCard}.is-minimal .${C.customTitle} {
        height:auto!important;
        -webkit-line-clamp:1!important;
        margin-bottom:0!important;
        padding-right:28px!important;
        font-size:12px!important;
    }

    .${C.processedCard}.is-minimal .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 0.25s 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 0.4s var(--fc2-ease-standard);
    }

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

    .${C.infoArea} {
        display:flex;
        flex-direction:column;
        justify-content:flex-end;
        flex-grow:1;
        padding:var(--fc2-space-base);
        background:rgba(255,255,255,0.03);
        border-top:1px solid var(--fc2-border);
        border-bottom-left-radius:var(--fc2-radius);
        border-bottom-right-radius:var(--fc2-radius);
    }

    .${C.customTitle} {
        display:-webkit-box;
        -webkit-line-clamp:2;
        -webkit-box-orient:vertical;
        height:38px;
        margin:0 0 var(--fc2-space-sm);
        color:var(--fc2-text)!important;
        font-size:13px;
        font-weight:600!important;
        line-height:1.5;
        text-decoration:none!important;
        overflow:hidden;
        transition:color 0.2s;
    }

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

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

    .${C.resourceLinksContainer} {
        display:flex;
        align-items:center;
        justify-content:flex-end;
        gap:var(--fc2-space-xs);
        margin-left:auto;
    }

    /* Visibility Control */
    .${C.processedCard}.${C.hideNoMagnet} { display:none!important; }
    .${C.processedCard}.${C.hideCensored} { display:none!important; }
    .${C.processedCard}.${C.hideBlocked} { display:none!important; }
`;
  const button = (C) => `
    /* ============================================================
       UNIFIED BUTTON SYSTEM
       ============================================================ */

    .card-top-right-controls {
        position:absolute;
        top:var(--fc2-space-sm);
        right:var(--fc2-space-sm);
        z-index:10;
        display:flex;
        gap:var(--fc2-space-xs);
        align-items:center;
    }

    .${C.resourceBtn},
    .card-top-right-controls > *,
    .verify-cf-btn {
        position:relative;
        display:inline-flex;
        align-items:center;
        justify-content:center;
        background:rgba(0,0,0,0.25);
        color:rgba(255,255,255,0.9);
        border:1px solid rgba(255,255,255,0.15);
        border-radius:var(--fc2-btn-radius);
        font-size:13px;
        font-weight:500;
        text-decoration:none;
        backdrop-filter:blur(8px);
        -webkit-backdrop-filter:blur(8px);
        cursor:pointer;
        transition:all 0.3s var(--fc2-ease-out);
    }

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

    .${C.resourceBtn}:hover,
    .card-top-right-controls > *:hover {
        background:var(--fc2-magnet-grad);
        color:#fff;
        border-color:transparent;
        transform:translateY(-2px) scale(1.02);
        box-shadow:var(--fc2-shadow-lg);
    }

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

    /* Micro-Interactions */
    .${C.resourceBtn}::after,
    .fc2-fab-btn::after,
    .fc2-enh-btn::after,
    .fc2-btn::after {
        content:"";
        position:absolute;
        top:50%;
        left:50%;
        width:100%;
        height:100%;
        background:rgba(var(--fc2-primary-rgb),0.2);
        opacity:0;
        border-radius:inherit;
        transform:translate(-50%,-50%) scale(1);
        pointer-events:none;
        transition:opacity 0.3s;
    }

    .${C.resourceBtn}:active::after,
    .fc2-fab-btn:active::after,
    .fc2-btn:active::after {
        animation:fc2-ripple 0.4s var(--fc2-ease-standard);
        opacity:1;
    }

    .verify-cf-btn {
        margin:var(--fc2-space-xs) auto;
        padding:var(--fc2-space-xs) var(--fc2-space-md);
        color:var(--fc2-warn)!important;
        border-color:rgba(250,179,135,0.3)!important;
    }

    .verify-cf-btn:hover {
        background:rgba(250,179,135,0.2)!important;
        color:var(--fc2-text)!important;
        border-color:var(--fc2-warn)!important;
        transform:translateY(-2px);
        box-shadow:0 4px 15px rgba(250,179,135,0.2)!important;
    }

    .card-top-right-controls > * {
        height:24px;
        padding:0 6px;
        font-size:10px;
    }

    .${C.resourceBtn} {
        height:32px;
        padding:0 12px;
        font-size:13px;
    }

    @container(max-width:250px) {
        .card-top-right-controls > * { height:20px; padding:0 4px; font-size:9px; }
        .card-top-right-controls { top:6px; right:6px; gap:3px; }
        .${C.resourceBtn} { height:24px; padding:0 6px; font-size:10px; }
    }

    @container(min-width:350px) {
        .card-top-right-controls > * { height:26px; padding:0 8px; font-size:11px; }
        .${C.resourceBtn} { height:30px; padding:0 12px; font-size:13px; }
    }

    .${C.resourceBtn}.${C.btnMagnet} {
        font-weight:600;
        margin-left:auto;
        background:rgba(0,0,0,0.25);
        border:1px solid rgba(255,255,255,0.15);
        animation:fc2-magnet-in 0.4s var(--fc2-ease-spring);
    }

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

    .btn-toggle-wanted:not(.is-wanted) .icon-wanted { display:none; }
    .btn-toggle-wanted:not(.is-wanted) .icon-unwanted { display:inline-flex; color:rgba(255,255,255,0.7); }
    .btn-toggle-wanted.is-wanted .icon-wanted { display:inline-flex; }
    .btn-toggle-wanted.is-wanted .icon-unwanted { display:none; }

    .btn-toggle-wanted.is-wanted {
        color:var(--fc2-accent);
        background:rgba(241,196,15,0.1);
        border-color:rgba(241,196,15,0.4);
        text-shadow:0 0 10px rgba(241,196,15,0.5);
    }

    .btn-toggle-wanted.fc2-star-burst::after {
        border-color: var(--fc2-accent);
    }

    /* 隐藏所有功能按钮的文字以保持图标化风格,同时覆盖工具栏和卡片 */
    .${C.processedCard} .${C.resourceBtn} .${C.buttonText},
    .enh-toolbar .${C.resourceBtn} .${C.buttonText} {
        display: none !important;
    }

    /* 确保 ID 徽章文字在任何容器下都保持显示 */
    .${C.processedCard} .${C.resourceBtn}.${C.fc2IdBadge} .${C.buttonText},
    .enh-toolbar .${C.resourceBtn}.${C.fc2IdBadge} .${C.buttonText} {
        display: inline-block !important;
    }

    .btn-toggle-blocked.is-blocked {
        color:var(--fc2-danger);
        border-color:rgba(243,139,168,0.4);
    }

    .btn-actress {
        display:inline-flex;
        align-items:center;
        justify-content:center;
        margin:var(--fc2-space-xs) auto;
        padding:var(--fc2-space-xs) var(--fc2-space-md);
        background:rgba(0,0,0,0.25);
        color:rgba(255,255,255,0.9);
        border:1px solid rgba(255,255,255,0.15);
        border-radius:var(--fc2-btn-radius);
        font-weight:500;
        backdrop-filter:blur(8px);
        -webkit-backdrop-filter:blur(8px);
        cursor:pointer;
        transition:all 0.3s var(--fc2-ease-out);
    }

    .btn-actress:hover {
        background:var(--fc2-magnet-grad);
        color:#fff;
        border-color:transparent;
        transform:translateY(-2px) scale(1.02);
        box-shadow:var(--fc2-shadow-lg);
    }

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

    .${C.fc2IdBadge} .${C.buttonText} {
        font-weight:600;
    }

    .${C.fc2IdBadge}:hover {
        border-color:rgba(255,255,255,0.4);
    }

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

    /* Button Industrial Style Override */
    .fc2-btn-industrial {
        position:relative;
        overflow:hidden;
        border:1px solid rgba(255,255,255,0.1)!important;
        background:rgba(255,255,255,0.03)!important;
        transition:all 0.2s var(--fc2-ease-out)!important;
    }

    .fc2-btn-industrial:active {
        transform:scale(0.97) translateY(2px)!important;
        background:rgba(0,0,0,0.2)!important;
        box-shadow:inset 0 2px 4px rgba(0,0,0,0.3)!important;
    }

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

    .fc2-enh-btn, .fc2-btn {
        height:38px;
        padding:0 1.5rem;
        background:rgba(255,255,255,0.05);
        color:var(--fc2-text-dim);
        border:1px solid var(--fc2-border);
        border-radius:var(--fc2-btn-radius);
        font-family:inherit;
        font-size:0.9rem;
        font-weight:600;
        cursor:pointer;
        transition:all 0.2s var(--fc2-ease-standard);
        display:inline-flex;
        align-items:center;
        justify-content:center;
        white-space:nowrap;
    }

    .fc2-enh-btn:hover, .fc2-btn:hover {
        background:rgba(255,255,255,0.1);
        color:var(--fc2-text);
        border-color:rgba(255,255,255,0.15);
    }

    .fc2-enh-btn.ghost, .fc2-btn.ghost {
        background:transparent;
        border-color:transparent;
        color:var(--fc2-text-dim);
    }

    .fc2-enh-btn.ghost:hover, .fc2-btn.ghost:hover {
        background:rgba(255,255,255,0.05);
        color:var(--fc2-text);
    }

    .fc2-enh-btn.micro, .fc2-btn.micro {
        height:28px;
        padding:0 0.75rem;
        font-size:12px!important;
        font-weight:500!important;
    }

    .fc2-enh-btn.icon-only, .fc2-btn.icon-only {
        width:38px;
        padding:0;
        justify-content:center;
    }

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

    .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:700;
    }

    .fc2-enh-btn.primary:hover, .fc2-btn.primary:hover {
        background:var(--fc2-text);
        transform:scale(1.02);
        box-shadow:0 4px 12px rgba(255,255,255,0.1);
    }

    .fc2-enh-btn.danger, .fc2-btn.danger {
        color:var(--fc2-danger);
        border-color:rgba(248,113,113,0.2);
    }

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

    /* Industrial feedback for buttons in panel */
    .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 0.2s var(--fc2-ease-out)!important;
    }

    .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(1px);
        box-shadow:inset 0 2px 4px rgba(0,0,0,0.2);
    }
`;
  const form = (_C) => `
    /* ============================================================
       FORM ELEMENTS (GLASSMORPHISM)
       ============================================================ */

    .fc2-enh-form-row {
        margin-bottom:var(--fc2-space-md);
    }

    .fc2-enh-label {
        display:block;
        margin-bottom:4px;
        color:var(--fc2-text-dim);
        font-size:13px;
        font-weight:500;
    }

    .fc2-enh-select, .fc2-enh-input, .fc2-enh-textarea {
        width:100%;
        padding:10px 14px;
        background:var(--fc2-bg)!important;
        color:var(--fc2-text)!important;
        border:1px solid rgba(255,255,255,0.08);
        border-radius:var(--fc2-btn-radius);
        font-family:inherit;
        font-size:14px;
        outline:none;
        transition:all 0.3s var(--fc2-ease-out);
        appearance:none;
        -webkit-appearance:none;
        will-change: border-color, box-shadow, background-color;
    }

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

    .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 3px rgba(var(--fc2-primary-rgb),0.1);
    }

    .fc2-enh-checkbox-label {
        display:flex;
        align-items:center;
        gap:10px;
        cursor:pointer;
        user-select:none;
        padding:4px 0;
    }

    input[type="checkbox"] {
        position:relative;
        appearance:none;
        -webkit-appearance:none;
        width:1.25rem;
        height:1.25rem;
        background:var(--fc2-surface-lowest);
        border:1px solid var(--fc2-border);
        border-radius:4px;
        cursor:pointer;
        transition:all 0.2s var(--fc2-ease-out);
        display:flex;
        align-items:center;
        justify-content:center;
    }

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

    input[type="checkbox"]::after {
        content:"";
        width:10px;
        height:10px;
        background-color:#000;
        clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0%,43% 62%);
        transform:scale(0);
        transition:transform 0.2s var(--fc2-ease-spring);
    }

    input[type="checkbox"]:checked::after {
        transform:scale(1);
    }

    .fc2-enh-checkbox-text {
        font-size:14px;
        color:var(--fc2-text);
        font-weight:600;
        opacity:0.9;
    }

    /* Range Input */
    .fc2-enh-range {
        -webkit-appearance:none;
        width:100%;
        height:4px;
        background:rgba(255,255,255,0.1);
        border-radius:2px;
        outline:none;
    }

    .fc2-enh-range::-webkit-slider-thumb {
        -webkit-appearance:none;
        width:14px;
        height:14px;
        border-radius:50%;
        background:var(--fc2-primary);
        cursor:pointer;
        box-shadow:0 0 0 2px var(--fc2-bg);
    }

    .fc2-enh-form-row.checkbox {
        justify-content:flex-start;
        gap:10px;
        padding:0.75rem 1rem;
        margin:0;
        border:1px solid var(--fc2-border);
        border-radius:var(--fc2-btn-radius);
        background:rgba(255, 255, 255, 0.02);
        transition:all 0.2s var(--fc2-ease-out);
        cursor:pointer;
        width:100%;
        box-sizing:border-box;
        will-change: background-color, border-color, box-shadow;
    }

    .fc2-enh-form-row.checkbox:hover {
        background:rgba(255,255,255,0.06);
        border-color:rgba(255, 255, 255, 0.2);
    }

    .fc2-enh-form-row.checkbox:has(input[type="checkbox"]:checked) {
        background: rgba(255, 255, 255, 0.08);
        border-color: var(--fc2-primary);
        box-shadow: 0 0 15px rgba(255, 255, 255, 0.05);
    }

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

    .fc2-input-group {
        display:flex;
        gap:8px;
        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:36px;
        height:36px;
        flex-shrink:0;
        padding:0;
        background:rgba(255,255,255,0.05);
        color:var(--fc2-text-dim);
        border:1px solid var(--fc2-border);
        border-radius:var(--fc2-btn-radius);
        cursor:pointer;
        transition:all 0.2s var(--fc2-ease-standard);
    }

    .fc2-input-toggle:hover {
        background:rgba(255,255,255,0.1);
        color:var(--fc2-text);
    }

    .fc2-input-toggle svg {
        width:14px;
        height:14px;
        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;
    }
`;
  const tabs = (_C) => `
    /* ============================================================
       TABS (SIDEBAR STYLE)
       ============================================================ */

    .fc2-enh-settings-tabs {
        display:flex;
        flex-direction:column;
        width:180px;
        padding:1.5rem 1.25rem;
        background:rgba(0,0,0,0.15);
        border-right:1px solid rgba(255,255,255,0.05);
        flex-shrink:0;
        gap:6px;
        overflow-y:auto;
        transition:width 0.4s var(--fc2-ease-out);
    }

    .fc2-enh-tab-btn {
        display:flex;
        align-items:center;
        gap:10px;
        width:100%;
        padding:10px 12px;
        background:transparent;
        color:var(--fc2-text-dim);
        border:none;
        border-radius:var(--fc2-radius-md);
        font-size:14px;
        font-weight:500;
        text-align:left;
        transition:all 0.2s var(--fc2-ease-standard);
        cursor:pointer;
    }

    .fc2-enh-tab-btn:hover {
        background:rgba(255,255,255,0.05);
        color:var(--fc2-text);
    }

    .fc2-enh-tab-btn.active {
        background:rgba(var(--fc2-primary-rgb),0.1);
        color:var(--fc2-primary);
        font-weight:600;
    }

    .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);
    }

    /* Tab Content Transitions */
    .fc2-tab-content-wrapper {
        height:100%;
        overflow-y:auto;
        padding:1.5rem 2rem;
        scrollbar-width:thin;
        transition: opacity 0.35s var(--fc2-ease-out), transform 0.4s var(--fc2-ease-out);
        opacity: 0;
        transform: translateY(15px) 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(-15px) scale(1.02);
        pointer-events: none;
    }

    /* Mobile Bottom Navigation */
    @media(max-width:768px) {
        .fc2-enh-settings-tabs {
            order:2;
            width:100%;
            height:auto;
            flex-direction:row;
            border-right:none;
            border-top:1px solid rgba(255,255,255,0.1);
            background:rgba(0,0,0,0.2);
            padding:0;
            overflow-x:auto;
            padding-bottom:env(safe-area-inset-bottom);
        }

        .fc2-enh-tab-btn {
            flex-direction:column;
            gap:4px;
            padding:8px 2px;
            font-size:10px;
            justify-content:center;
            border-radius:0;
            flex:1;
            min-width:60px;
        }

        .fc2-enh-tab-btn .fc2-icon {
            font-size:1.4em;
            margin-bottom:2px;
        }
    }
`;
  const dashboard = (_C) => `
    /* ============================================================
       DASHBOARD (MANAGEMENT CENTER HERO)
       ============================================================ */

    .fc2-dashboard-grid {
        display:grid;
        grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
        gap:1.5rem;
        width:100%;
    }

    .fc2-dashboard-card {
        display:flex;
        flex-direction:column;
        padding:1.5rem;
        background:rgba(255,255,255,0.02);
        border:1px solid rgba(255,255,255,0.05);
        border-radius:var(--fc2-radius-lg);
        transition:all 0.3s var(--fc2-ease-out);
        position:relative;
        overflow:hidden;
    }

    .fc2-dashboard-card:hover {
        background:rgba(255,255,255,0.04);
        border-color:rgba(255,255,255,0.1);
        transform:translateY(-2px);
    }

    .fc2-dashboard-card h4 {
        margin:0 0 1.25rem 0;
        font-size:0.95rem;
        font-weight:600;
        color:var(--fc2-text-dim);
        display:flex;
        align-items:center;
        gap:10px;
    }

    .fc2-dashboard-card .fc2-icon {
        color:var(--fc2-primary);
        font-size:1.2em;
    }

    .fc2-stat-value {
        font-family:var(--fc2-font-mono);
        font-size:2.5rem;
        font-weight:700;
        color:var(--fc2-text);
        line-height:1;
        margin-bottom:0.5rem;
    }

    .fc2-stat-label {
        font-size:0.85rem;
        color:var(--fc2-text-dim);
        opacity:0.7;
    }

    /* LED Status Indicators */
    .fc2-led-dot {
        display:inline-block;
        width:8px;
        height:8px;
        border-radius:50%;
        margin-right:10px;
        background:var(--fc2-text-muted);
        position:relative;
    }
    
    .fc2-led-group {
        display: flex;
        align-items: center;
        margin-bottom: 8px;
    }

    .fc2-led-dot.active {
        background:var(--fc2-primary);
        box-shadow:0 0 10px rgba(var(--fc2-primary-rgb),0.5);
    }

    .fc2-led-dot.active::after {
        content:'';
        position:absolute;
        inset:-2px;
        border-radius:50%;
        border:1px solid var(--fc2-primary);
        animation:fc2-led-pulse 2s infinite;
    }

    @keyframes fc2-led-pulse {
        0% { transform:scale(1); opacity:0.8; }
        100% { transform:scale(2.5); opacity:0; }
    }

    .fc2-card-actions {
        display:flex;
        gap:8px;
        margin-top:auto;
        padding-top:12px;
    }

    .fc2-card-subtitle {
        font-size:12px!important;
        font-weight:400!important;
        color:var(--fc2-text-muted);
        margin-left:auto;
    }

    .fc2-stat-unit {
        font-size:0.5em;
        margin-left:4px;
        opacity:0.6;
    }
`;
  const collection = (C) => `
    /* ============================================================
       COLLECTION GRID (FLUID RESIZE)
       ============================================================ */

    .fc2-collection-grid {
        display:grid;
        grid-template-columns:repeat(auto-fill,minmax(var(--fc2-coll-width,200px),1fr));
        gap:1.5rem;
        width:100%;
        margin-top:10px;
        transition:gap 0.2s;
    }

    .fc2-collection-container {
        display:flex;
        flex-direction:column;
        gap:10px;
        width:100%;
    }

    @media(max-width:768px) {
        .fc2-collection-grid {
            gap:0.75rem;
            grid-template-columns:repeat(auto-fill,minmax(140px,1fr))!important;
        }
    }

    .fc2-collection-toolbar {
        display:flex!important;
        flex-wrap:wrap!important;
        gap:0.75rem!important;
        align-items:center!important;
        padding:0.75rem 1rem!important;
        overflow-x:visible!important;
    }

    .fc2-collection-toolbar .filter-group {
        display:flex;
        align-items:center;
        gap:0.5rem;
        flex-wrap:wrap;
    }

    .fc2-collection-toolbar .filter-group.primary {
        flex:1 1 200px;
    }

    .fc2-collection-toolbar .filter-group:first-child {
        flex-shrink:1;
        min-width:120px;
    }

    .fc2-collection-toolbar.stats-display {
        font-size:11px;
        opacity:0.7;
        margin-left:4px;
        white-space:nowrap;
    }

    .fc2-card-remove-overlay {
        position:absolute;
        top:8px;
        right:8px;
        width:24px;
        height:24px;
        background:rgba(239,68,68,0.8);
        color:white;
        border-radius:50%;
        display:flex;
        align-items:center;
        justify-content:center;
        cursor:pointer;
        opacity:0;
        transition:all 0.2s ease;
        z-index:10;
        font-size:18px;
        backdrop-filter:blur(4px);
    }

    .${C.cardRebuilt}:hover .fc2-card-remove-overlay {
        opacity:1;
    }

    .fc2-card-remove-overlay:hover {
        background:#ef4444;
        transform:scale(1.1);
    }

    .fc2-batch-action-bar {
        animation:fc2-fade-in-scale 0.3s ease-out;
        backdrop-filter:blur(8px);
    }

    .${C.cardRebuilt}.is-selected {
        border-color:var(--fc2-primary)!important;
        box-shadow:0 0 15px rgba(var(--fc2-primary-rgb),0.4)!important;
    }

    .${C.cardRebuilt} .resource-btn:not(.${C.fc2IdBadge}) .${C.buttonText},
    .enh-toolbar .resource-btn:not(.${C.fc2IdBadge}) .${C.buttonText} {
        display:none!important;
    }

    .toolbar-group {
        display:flex;
        align-items:center;
        gap:8px;
    }

    .toolbar-group.search {
        flex:1 1 200px;
        min-width:0;
    }

    .toolbar-group.filters {
        display:flex;
        gap:8px;
        flex-shrink:0;
    }

    .toolbar-group.actions {
        display:flex;
        gap:8px;
        flex-shrink:0;
        margin-left:auto;
    }

    .input-wrapper {
        position:relative;
        display:flex;
        align-items:center;
        flex:1;
        min-width:0;
    }

    .input-wrapper .fc2-enh-input {
        width:100%;
        padding-right:28px;
    }

    .fc2-search-clear {
        position:absolute;
        right:8px;
        top:50%;
        transform:translateY(-50%);
        width:20px;
        height:20px;
        display:flex;
        align-items:center;
        justify-content:center;
        background:rgba(255,255,255,0.1);
        color:var(--fc2-text-dim);
        border:none;
        border-radius:50%;
        font-size:14px!important;
        cursor:pointer;
        transition:all 0.15s;
        padding:0;
        line-height:1;
    }

    .fc2-search-clear:hover {
        background:rgba(255,255,255,0.2);
        color:var(--fc2-text);
    }

    .fc2-back-to-top {
        position:fixed;
        bottom:24px;
        right:24px;
        width:40px;
        height:40px;
        display:flex;
        align-items:center;
        justify-content:center;
        background:rgba(0,0,0,0.6);
        color:var(--fc2-text);
        border:1px solid var(--fc2-border);
        border-radius:50%;
        cursor:pointer;
        z-index:var(--fc2-z-dropdown);
        backdrop-filter:blur(8px);
        -webkit-backdrop-filter:blur(8px);
        transition:all 0.2s var(--fc2-ease-standard);
        box-shadow:var(--fc2-shadow-md);
    }

    .fc2-back-to-top:hover {
        background:rgba(255,255,255,0.15);
        transform:translateY(-2px);
    }

    .fc2-back-to-top .fc2-icon {
        font-size:1.2em;
    }

    .fc2-no-results {
        display:flex;
        flex-direction:column;
        align-items:center;
        justify-content:center;
        gap:12px;
        padding:4rem 2rem;
        color:var(--fc2-text-muted);
        grid-column:1/-1;
    }

    .fc2-no-results .icon {
        font-size:2rem;
        opacity:0.4;
    }

    .fc2-no-results .icon svg {
        width:32px;
        height:32px;
        fill:currentColor;
    }

    .fc2-no-results .text {
        font-size:14px!important;
    }

    .fc2-health-progress {
        display:flex;
        align-items:center;
        gap:6px;
        padding:4px 10px;
        background:rgba(var(--fc2-primary-rgb),0.08);
        border:1px solid rgba(var(--fc2-primary-rgb),0.15);
        border-radius:var(--fc2-radius-sm);
        font-size:12px!important;
        color:var(--fc2-primary);
        white-space:nowrap;
        animation:fc2-pulse 2s infinite;
    }

    .fc2-health-progress .fc2-icon {
        font-size:0.9em;
    }

    .fc2-gallery-sentinel {
        height:1px;
        width:100%;
        grid-column:1/-1;
    }

    .fc2-batch-action-bar {
        display:flex;
        align-items:center;
        justify-content:space-between;
        gap:12px;
        padding:10px 16px;
        background:rgba(var(--fc2-primary-rgb),0.05);
        border:1px solid rgba(var(--fc2-primary-rgb),0.15);
        border-radius:var(--fc2-radius-md);
        animation:fc2-fade-in 0.3s var(--fc2-ease-out);
        backdrop-filter:blur(8px);
    }

    .fc2-batch-action-bar .batch-info {
        display:flex;
        align-items:center;
        gap:8px;
    }

    .fc2-batch-action-bar .batch-info .count {
        font-weight:600!important;
        color:var(--fc2-primary);
    }

    .fc2-batch-action-bar .batch-actions {
        display:flex;
        gap:8px;
    }

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

    @media(max-width:768px) {
        .fc2-collection-toolbar { flex-direction:column; align-items:stretch!important; }
        .toolbar-group { flex-wrap:wrap; }
        .toolbar-group.actions { margin-left:0; }
        .fc2-back-to-top { bottom:80px; right:16px; }
        .fc2-batch-action-bar { flex-direction:column; gap:8px; }
    }
`;
  const settings = (_C) => `
    /* ============================================================
       SETTINGS GROUPS & PORTALS
       ============================================================ */

    .fc2-enh-settings-group {
        display:flex;
        flex-direction:column;
        gap:12px;
        padding:1.25rem 1.5rem;
        background:rgba(255,255,255,0.015);
        border:1px solid rgba(255,255,255,0.03);
        border-radius:var(--fc2-radius-md);
        transition:all 0.2s var(--fc2-ease-standard);
        height:100%;
        position:relative;
        will-change: background-color, border-color;
    }

    .fc2-enh-settings-group:hover {
        background:rgba(255,255,255,0.04);
        border-color:rgba(255,255,255,0.15);
    }

    .fc2-enh-settings-group h3 {
        margin-top:0;
        margin-bottom:1rem;
        padding-bottom:0.75rem;
        font-weight:700;
        text-transform:uppercase;
        letter-spacing:0.05em;
        opacity:0.9;
    }

    .portal-grid {
        display:grid;
        grid-template-columns:repeat(auto-fill,minmax(180px,1fr));
        gap:0.6rem 1.25rem;
        margin-top:0.5rem;
    }

    .portal-item {
        display:flex;
        align-items:center;
        gap:10px;
        padding:0.75rem 1rem;
        border:1px solid var(--fc2-border);
        border-radius:var(--fc2-btn-radius);
        cursor:pointer;
        transition:all 0.2s var(--fc2-ease-out);
        background: rgba(255, 255, 255, 0.02);
        will-change: transform, background-color, border-color, box-shadow;
    }

    .portal-item span {
        color: var(--fc2-text) !important;
        font-weight: 600 !important;
        font-size: 14px !important;
        opacity: 0.9;
    }

    .portal-item:hover {
        background:rgba(255,255,255,0.06);
        border-color:rgba(255, 255, 255, 0.2);
    }

    .portal-item.active {
        background: rgba(255, 255, 255, 0.08);
        border-color: var(--fc2-primary);
        box-shadow: 0 0 15px rgba(255, 255, 255, 0.05);
    }

    .portal-item.active span {
        opacity: 1;
        color: var(--fc2-text) !important;
    }

    .fc2-filter-chip {
        padding:4px 12px;
        background:rgba(255,255,255,0.05);
        border:1px solid var(--fc2-border);
        border-radius:var(--fc2-radius-lg);
        font-size:12px;
        cursor:pointer;
        transition:all 0.2s ease;
        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 10px rgba(var(--fc2-primary-rgb),0.3);
    }

    .fc2-settings-grid {
        display:grid;
        grid-template-columns:repeat(auto-fill,minmax(320px,1fr));
        gap:1.5rem;
    }

    .fc2-settings-card-grid {
        display:grid;
        grid-template-columns:repeat(auto-fill,minmax(220px,1fr));
        gap:10px 16px;
    }

    .fc2-grid-actions {
        display:flex;
        flex-wrap:wrap;
        gap:8px;
        margin-bottom:16px;
    }

    .fc2-auth-section {
        margin-top:12px;
        padding-top:12px;
        border-top:1px solid var(--fc2-border);
    }

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

    .fc2-portal-actions {
        display:flex;
        gap:8px;
        margin-bottom:16px;
    }

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

    .fc2-debug-container {
        display:flex;
        flex-direction:column;
        gap:12px;
        height:100%;
    }

    .fc2-debug-header {
        display:flex;
        flex-direction:column;
        gap:10px;
        flex-shrink:0;
    }

    .fc2-debug-actions {
        display:flex;
        gap:8px;
        flex-wrap:wrap;
    }

    .fc2-debug-filters {
        display:flex;
        align-items:center;
        gap:12px;
        flex-wrap:wrap;
    }

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

    .fc2-log-item {
        display:flex;
        align-items:flex-start;
        gap:8px;
        padding:6px 8px;
        border-bottom:1px solid rgba(255,255,255,0.03);
        font-family:var(--fc2-font-mono)!important;
        font-size:12px!important;
        line-height:1.6!important;
        transition:background 0.15s;
    }

    .fc2-log-item:hover { background:rgba(255,255,255,0.03); }
    .fc2-log-item:last-child { border-bottom:none; }

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

    .fc2-log-time { color:var(--fc2-text-muted); white-space:nowrap; flex-shrink:0; }
    .fc2-log-level { font-weight:600!important; white-space:nowrap; flex-shrink:0; min-width:56px; }
    .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; }

    .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:6px 0 0 0;
        padding:8px;
        background:rgba(0,0,0,0.3);
        border-radius:var(--fc2-radius-sm);
        font-size:11px!important;
        color:var(--fc2-text-dim);
        overflow-x:auto;
        white-space:pre-wrap;
        word-break:break-all;
    }

    .fc2-log-payload-toggle {
        padding:2px 8px;
        background:rgba(255,255,255,0.05);
        border:1px solid var(--fc2-border);
        border-radius:var(--fc2-radius-sm);
        color:var(--fc2-text-dim);
        font-size:11px!important;
        cursor:pointer;
        margin-left:auto;
        flex-shrink:0;
        transition:all 0.15s;
    }

    .fc2-log-payload-toggle:hover {
        background:rgba(255,255,255,0.1);
        color:var(--fc2-text);
    }

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

    .fc2-about-tab {
        display:flex;
        flex-direction:column;
        gap:1.5rem;
    }

    .fc2-about-header {
        text-align:center;
        padding:2rem 0 1rem;
    }

    .fc2-version-badge {
        display:inline-block;
        margin-top:8px;
        padding:4px 12px;
        background:rgba(var(--fc2-primary-rgb),0.1);
        border:1px solid rgba(var(--fc2-primary-rgb),0.2);
        border-radius:var(--fc2-radius-full);
        font-size:12px!important;
        font-weight:600!important;
        color:var(--fc2-primary);
        letter-spacing:0.05em;
    }

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

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

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

    .fc2-about-footer {
        text-align:center;
        padding:1.5rem 0;
        font-size:13px!important;
        color:var(--fc2-text-muted);
    }

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

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

    .fc2-dashboard-card.warning {
        border-color:rgba(250,179,135,0.2);
    }

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

    .fc2-dashboard-card.full-width {
        grid-column:1/-1;
    }

    @media(max-width:768px) {
        .fc2-settings-grid { grid-template-columns:1fr; }
        .fc2-settings-card-grid { grid-template-columns:1fr; }
        .fc2-grid-actions { flex-direction:column; }
        .fc2-debug-filters { gap:8px; }
        .fc2-log-item { flex-wrap:wrap; gap:4px; }
        .fc2-log-time, .fc2-log-module { display:none; }
    }
`;
  const modal = (_C) => `
    /* ============================================================
       MODAL & SETTINGS PANEL
       ============================================================ */

    .enh-modal-backdrop {
        position:fixed;
        inset:0;
        z-index:var(--fc2-z-overlay);
        background:rgba(0,0,0,0.4);
        transition:all 0.3s var(--fc2-ease-standard);
        pointer-events: auto;
    }

    .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:1px 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), 0 40px 100px -20px rgba(0,0,0,0.8);
        overflow:hidden;
        animation:fc2-pop-in 0.4s var(--fc2-ease-out);
        transform:translate(-50%,-50%);
        will-change:transform;
        pointer-events: auto;
    }

    .fc2-enh-settings-panel {
        width:100vw;
        height:100dvh;
        display:flex;
        flex-direction:column;
        overflow:hidden;
        position:fixed;
        top:0;
        left:0;
        z-index:var(--fc2-z-modal) !important;
        transform:none !important;
        background:var(--fc2-liquid-bg);
        backdrop-filter:none;
        -webkit-backdrop-filter:none;
        box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08);
        pointer-events: auto;
    }

    .fc2-enh-settings-header {
        display:flex;
        align-items:center;
        justify-content:space-between;
        padding:0.85rem 1.75rem;
        background:rgba(255,255,255,0.03);
        border-bottom:1px solid rgba(255,255,255,0.05);
        flex-shrink:0;
        z-index:10;
    }

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

    .fc2-enh-settings-content {
        position:relative;
        flex:1;
        min-height:0;
        padding:0;
        overflow:hidden;
        background:transparent;
    }

    .fc2-enh-settings-footer {
        display:flex;
        justify-content:flex-end;
        gap:1rem;
        padding:1.25rem 2rem;
        background:rgba(0,0,0,0.15);
        border-top:1px solid var(--fc2-border);
        flex-shrink:0;
    }

    .close-btn {
        display:inline-flex;
        align-items:center;
        justify-content:center;
        width:36px;
        height:36px;
        padding:0;
        background:transparent;
        color:var(--fc2-text-dim);
        border:1px solid transparent;
        border-radius:var(--fc2-radius-sm);
        cursor:pointer;
        transition:all 0.2s var(--fc2-ease-standard);
    }

    .close-btn:hover {
        background:rgba(255,255,255,0.08);
        color:var(--fc2-text);
        border-color:rgba(255,255,255,0.1);
    }

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

    .fc2-loading-spinner {
        width:32px;
        height:32px;
        border:2px solid rgba(255,255,255,0.1);
        border-top-color:var(--fc2-primary);
        border-radius:50%;
        animation:fc2-spin 0.8s linear infinite;
    }

    .fc2-loading-text {
        font-size:13px!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:14px!important;
    }

    @media(max-width:768px) {
        .fc2-enh-settings-body { flex-direction:column; }
        .fc2-enh-settings-content { order:1; padding:1rem; }
        .fc2-enh-settings-header { padding:0.75rem 1rem; }
        .fc2-enh-settings-footer { padding:0.75rem 1rem; padding-bottom:max(0.75rem,env(safe-area-inset-bottom)); }
    }
`;
  const misc = (C) => `
    /* ============================================================
       PLAYER TOOLBAR (DESKTOP)
       ============================================================ */

    .enh-toolbar {
        width: 100%;
        margin: var(--fc2-space-md) 0;
        background: var(--fc2-surface);
        border: 1px solid var(--fc2-border);
        border-radius: var(--fc2-radius-lg);
        padding: var(--fc2-space-sm) var(--fc2-space-md);
        box-shadow: var(--fc2-shadow-sm);
        backdrop-filter: blur(var(--fc2-blur));
        -webkit-backdrop-filter: blur(var(--fc2-blur));
        box-sizing: border-box;
    }

    .enh-toolbar .info-area {
        display: grid !important;
        grid-template-columns: 1fr fit-content(50%) 1fr !important;
        align-items: center !important;
        width: 100% !important;
        gap: var(--fc2-space-sm) !important;
    }

    .enh-toolbar .resource-btn {
        flex-shrink: 0 !important;
        width: auto !important;
    }

    .enh-toolbar .card-top-right-controls {
        position: static !important;
        display: flex !important;
        flex-direction: row !important;
        flex-wrap: nowrap !important;
        gap: var(--fc2-space-sm) !important;
        align-items: center !important;
        justify-content: flex-start !important;
        min-width: 0 !important;
        overflow: hidden !important;
    }

    .enh-toolbar .btn-actress {
        margin: 0 !important;
        white-space: nowrap !important;
        overflow: hidden !important;
        text-overflow: ellipsis !important;
        min-width: 0 !important;
        max-width: 100% !important;
        text-align: center !important;
        padding: 0 var(--fc2-space-md) !important;
    }

    .enh-toolbar .resource-links-container {
        display: flex !important;
        flex-direction: row !important;
        flex-wrap: nowrap !important;
        gap: var(--fc2-space-sm) !important;
        align-items: center !important;
        justify-content: flex-end !important;
        min-width: 0 !important;
        overflow: hidden !important;
    }

    /* ============================================================
       DROPDOWN & TOOLTIP
       ============================================================ */

    .enh-dropdown { position:relative; display:inline-flex; }
    .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:140px;
        padding:var(--fc2-space-sm);
        background:rgba(0,0,0,0.6);
        border:1px solid rgba(255,255,255,0.12);
        border-radius:var(--fc2-radius-md);
        box-shadow:var(--fc2-shadow-lg);
        backdrop-filter:blur(16px);
        -webkit-backdrop-filter:blur(16px);
        animation:fc2-dropdown-in 0.2s var(--fc2-ease-standard);
    }

    .enh-dropdown.active .enh-dropdown-content { display:flex; }

    .tooltip {
        display:none;
        position:absolute;
        bottom:100%;
        left:50%;
        transform:translateX(-50%) translateY(-8px);
        padding:5px 8px;
        background:rgba(0,0,0,0.9);
        color:#fff;
        border-radius:4px;
        font-size:11px;
        white-space:nowrap;
        pointer-events:none;
        opacity:0;
        transition:opacity 0.2s;
        z-index:var(--fc2-z-tooltip);
    }

    .${C.resourceBtn}:hover .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(255,255,255,0.03) 20%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.03) 80%, transparent);
        background-size:200% 100%;
        animation:fc2-shimmer 2.5s infinite linear;
    }

    /* ============================================================
       GALLERY & VIEWER
       ============================================================ */
    .enh-viewer-backdrop {
        position:fixed;
        inset:0;
        z-index:var(--fc2-z-gallery);
        display:flex;
        flex-direction:column;
        background:var(--fc2-surface-float);
        backdrop-filter:blur(24px) saturate(180%);
        -webkit-backdrop-filter:blur(24px) saturate(180%);
        pointer-events: auto;
    }

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

    .enh-viewer-stage img, .enh-viewer-stage video {
        max-width:100%;
        max-height:100%;
        object-fit:contain;
        border-radius:var(--fc2-radius-lg);
        box-shadow:0 20px 60px rgba(0,0,0,0.8);
        transition:transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
        will-change:transform;
    }

    @keyframes fc2-slide-in-right {
        from { transform:translateX(50px); opacity:0; }
        to { transform:translateX(0); opacity:1; }
    }

    @keyframes fc2-slide-in-left {
        from { transform:translateX(-50px); opacity:0; }
        to { transform:translateX(0); opacity:1; }
    }

    @keyframes fc2-fade-in {
        from { opacity:0; transform:scale(0.98); }
        to { opacity:1; transform:scale(1); }
    }

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

    .enh-viewer-nav {
        position:absolute;
        top:50%;
        transform:translateY(-50%);
        z-index:var(--fc2-z-toast);
        width:48px;
        height:48px;
        display:flex;
        align-items:center;
        justify-content:center;
        background:rgba(255,255,255,0.1);
        border-radius:50%;
        cursor:pointer;
        transition:all 0.2s;
        /* backdrop-filter:blur(4px); Removed for performance */
        color: white;
    }

    .enh-viewer-nav:hover {
        background:rgba(255,255,255,0.2);
        transform:translateY(-50%) scale(1.1);
    }

    .enh-viewer-nav.prev { left:20px; }
    .enh-viewer-nav.next { right:20px; }

    .enh-viewer-close {
        position:absolute;
        top:20px;
        right:20px;
        z-index:var(--fc2-z-toast);
        width:40px;
        height:40px;
        display:flex;
        align-items:center;
        justify-content:center;
        background:rgba(0,0,0,0.2);
        border-radius:50%;
        cursor:pointer;
        color:white;
        transition:background 0.2s;
    }

    .enh-viewer-close:hover { background:rgba(0,0,0,0.5); }

    .enh-viewer-actions {
        position:absolute;
        bottom:100px;
        right:20px;
        z-index:var(--fc2-z-toast);
        display:flex;
        flex-direction:column;
        gap:10px;
    }

    .enh-viewer-action {
        width:40px;
        height:40px;
        border-radius:50%;
        background:rgba(0,0,0,0.4);
        border:1px solid rgba(255,255,255,0.1);
        color:white;
        display:flex;
        align-items:center;
        justify-content:center;
        cursor:pointer;
        transition:all 0.2s;
    }

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

    .enh-viewer-counter {
        position:absolute;
        top:20px;
        left:20px;
        z-index:var(--fc2-z-toast);
        background:rgba(0,0,0,0.5);
        padding:4px 12px;
        border-radius:var(--fc2-radius-xl);
        color:white;
        font-size:14px;
        /* backdrop-filter:blur(4px); Removed for performance */
    }

    .enh-viewer-thumbs {
        height:80px;
        width:100%;
        display:flex;
        gap:10px;
        padding:10px 20px;
        overflow-x:auto;
        background:rgba(0,0,0,0.3);
        /* backdrop-filter:blur(10px); Removed for performance */
        z-index:var(--fc2-z-toast);
    }

    .enh-thumb-item {
        height:100%;
        aspect-ratio:16/9;
        border-radius:6px;
        overflow:hidden;
        cursor:pointer;
        opacity:0.5;
        transition:opacity 0.2s;
        border:2px solid transparent;
        flex-shrink:0;
    }

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

    .enh-thumb-item img, .enh-thumb-item video {
        width:100%;
        height:100%;
        object-fit:cover;
    }
`;
  const actionsheet = (_C) => `
    /* ============================================================
       ACTION SHEET (MOBILE)
       ============================================================ */
    .fc2-action-sheet-backdrop {
        position:fixed;
        inset:0;
        z-index:var(--fc2-z-actionsheet);
        opacity:0;
        will-change: opacity, visibility;
        visibility:hidden;
        transition:all 0.3s 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-actionsheet) + 1);
        transform:translateY(100%);
        transition:transform 0.4s var(--fc2-ease-out);
        padding-bottom:calc(var(--fc2-space-lg) + env(safe-area-inset-bottom,0px));
        border:1px solid rgba(255,255,255,0.1);
        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:0 32px 64px rgba(0,0,0,0.6);
        opacity:0;
        visibility:hidden;
        pointer-events:none;
        transition:all 0.3s 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:16px 20px;
        border-bottom:1px solid rgba(255,255,255,0.1);
    }

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

    .fc2-action-sheet-close-btn {
        background:transparent;
        border:none;
        color:var(--fc2-text-dim);
        font-size:20px!important;
        cursor:pointer;
        padding:4px;
        line-height:1;
        transition:color 0.2s;
    }

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

    .fc2-action-sheet-grid {
        display:grid;
        grid-template-columns:repeat(auto-fill,minmax(100px,1fr));
        gap:12px;
        padding:20px;
        max-height:60vh;
        overflow-y:auto;
    }

    .fc2-action-sheet-item {
        display:flex;
        flex-direction:column;
        align-items:center;
        gap:8px;
        padding:12px;
        background:rgba(255,255,255,0.05);
        border:1px solid rgba(255,255,255,0.05);
        border-radius:var(--fc2-radius-md);
        text-decoration:none!important;
        color:var(--fc2-text-dim)!important;
        transition:all 0.2s;
        text-align:center;
    }

    .fc2-action-sheet-item:hover {
        background:rgba(var(--fc2-primary-rgb),0.1);
        border-color:var(--fc2-primary);
        color:var(--fc2-primary)!important;
        transform:translateY(-2px);
    }

    .fc2-action-sheet-item .fc2-icon {
        font-size:24px;
        margin-bottom:4px;
    }

    .fc2-action-sheet-item span:last-child {
        font-size:12px!important;
        white-space:nowrap;
        overflow:hidden;
        text-overflow:ellipsis;
        width:100%;
    }
`;
  const getComponentStyles = (C) => `
    ${tokens}
    ${base()}
    ${toast()}
    ${fab()}
    ${card(C)}
    ${button(C)}
    ${form()}
    ${tabs()}
    ${dashboard()}
    ${collection(C)}
    ${settings()}
    ${modal()}
    ${misc(C)}
    ${actionsheet()}
`;
  const getMobileStyles = (C) => `
    /* ============================================================
       MOBILE TOUCH OPTIMIZATIONS
       ============================================================ */

    @media (max-width: 768px) {
        * {
            /* Prevent double-tap zoom on mobile */
            touch-action: manipulation;
        }
        
        body {
            /* Smooth scrolling on mobile */
            -webkit-overflow-scrolling: touch;
        }

        html {
            /* Prevent horizontal scroll if possible, but don't force width */
            overflow-x: hidden !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,
            .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(255, 255, 255, 0.1) !important;
            }
        }
        
        /* Sharper animations for mobile performance */
        * {
            animation-duration: 0.2s !important;
            transition-duration: 0.2s !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: 1rem !important;
        }

        .fc2-enh-tab-btn {
            display: flex;
            flex: 1;
            width: auto !important;
            justify-content: center;
            padding: 0 0.5rem !important;
            height: 36px !important;
            font-size: 0.85rem !important;
            border-radius: 8px !important;
        }
        
        .fc2-enh-form-row {
            flex-direction: column !important;
            align-items: flex-start !important;
            gap: 0.5rem !important;
            padding: 0.75rem 0 !important;
            border-bottom: 1px solid rgba(255,255,255,0.05) !important;
        }

        /* Prevent checkboxes from splitting on mobile */
        .fc2-enh-form-row.checkbox {
            flex-direction: row !important;
            align-items: center !important;
            padding: 0.75rem 0.5rem !important;
        }

        .fc2-enh-settings-group {
            margin-bottom: 1.5rem !important;
            padding: 0 !important;
        }

        .portal-grid {
            grid-template-columns: repeat(2, 1fr) !important;
            gap: 8px !important;
        }

        .portal-item {
            padding: 8px !important;
            font-size: 13px !important;
        }

        .data-management-actions {
            flex-direction: row !important;
            flex-wrap: wrap !important;
            gap: 8px !important;
        }

        .data-management-actions > * {
            flex: 1 1 calc(50% - 8px) !important;
            min-width: 120px !important;
            height: 44px !important;
        }


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

        /* Force single column layout on common containers - tightening to avoid site collisions */
        div.grid, 
        .post-list,
        .posts:not(.video-player):not(.video-container), 
        div.flex-wrap:not(.entry-content), 
        .movie-list, 
        .work-list, 
        .artist-list,
        #artist-list,
        #work-list,
        .tile-images { 
            display: grid !important; 
            grid-template-columns: 1fr !important; 
            gap: 12px !important; 
            width: 100% !important;
            max-width: 100% !important;
            padding: 10px !important;
            box-sizing: border-box !important;
            margin: 0 !important;
        }

        .container, 
        .main-content, 
        #main, 
        #content {
            width: 100% !important;
            max-width: 100% !important;
            box-sizing: border-box !important;
        }
        
        .${C.cardRebuilt} {
            width: 100% !important;
            max-width: 100% !important;
            margin: 0 !important;
            overflow: hidden !important;
            flex-basis: 100% !important; /* For flex containers */
        }
        
        /* Larger Touch Targets for Mobile */
        .card-top-right-controls { 
            top: 12px !important; 
            right: 12px !important; 
            gap: 10px !important; 
        }

        .card-top-right-controls > * { 
            min-height: 44px;
            min-width: 44px;
            height: 36px !important; 
            padding: 0 12px !important; 
            font-size: 14px !important; 
        }

        .${C.resourceBtn} { 
            min-height: 44px;
            height: 44px !important; 
            padding: 0 16px !important; 
            font-size: 14px !important; 
        }

        .btn-actress { 
            width: 90% !important; 
            margin: 8px auto !important; 
            padding: 8px 16px !important; 
            font-size: 15px !important; 
        }

        .fc2-fab-trigger { 
            width: 56px !important; 
            height: 56px !important; 
            font-size: 24px !important; 
        }

        .fc2-fab-btn { 
            width: 48px !important; 
            height: 48px !important; 
            font-size: 20px !important; 
        }
        
        /* ============================================================
           TOOLBAR & MODAL MOBILE
           ============================================================ */

        .enh-toolbar { 
            display: flex !important;
            height: auto !important; 
            min-height: 60px !important; 
            margin: 10px 0 !important; 
            border-radius: var(--fc2-radius-md) !important; 
            overflow-x: auto !important;
        }

        .enh-toolbar .info-area { 
            display: flex !important; 
            flex-direction: row !important; 
            flex-wrap: nowrap !important;
            gap: 12px !important;
            width: auto !important;
            min-width: 100% !important;
            height: auto !important; 
            padding: 12px !important; 
            align-items: center !important;
        }

        .enh-toolbar .card-top-right-controls,
        .enh-toolbar .btn-actress,
        .enh-toolbar .resource-links-container { 
            display: flex !important;
            flex-direction: row !important;
            flex-wrap: nowrap !important;
            justify-content: center !important; 
            align-items: center !important;
            width: auto !important; 
            margin: 0 !important;
            flex-shrink: 0 !important;
        }

        .enh-toolbar .resource-links-container { 
            flex-wrap: nowrap !important; 
            gap: 10px !important; 
        }

        .enh-toolbar .resource-links-container .${C.resourceBtn} { 
            flex: 1 !important; 
            width: auto !important; 
            min-width: 80px !important; 
        }
        
        .enh-dropdown-content {
            min-width: 160px !important;
            max-width: 90vw !important;
        }

        .enh-dropdown-content .${C.resourceBtn} {
            height: 44px !important;
            font-size: 14px !important;
        }
        
        .enh-viewer-nav { 
            display: none !important; /* Hide arrows on mobile, rely on swipe */
        }
        
        .enh-viewer-thumbs {
            bottom: 20px !important;
            width: 95% !important;
            height: 50px !important;
            gap: 6px !important;
        }

        .enh-thumb-item {
            width: 44px !important;
            height: 44px !important;
        }

        .enh-viewer-actions {
            top: 10px !important;
            padding: 4px !important;
        }

        .enh-viewer-action {
            width: 44px !important;
            height: 44px !important;
        }

        .enh-viewer-close { 
            top: 10px !important; 
            right: 10px !important; 
            width: 48px !important; 
            height: 48px !important; 
            background: rgba(0,0,0,0.5) !important;
            border-radius: 50% !important;
            backdrop-filter: blur(8px) !important;
        }
        
        .enh-modal-panel {
            width: 95% !important;
            max-width: 95% !important;
            max-height: 90vh !important;
        }

        /* Improved Grid Responsiveness */
        .preview-hero-section {
            margin-bottom: 15px !important;
        }

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

        .${C.extraPreviewGrid} {
            grid-template-columns: repeat(2, 1fr) !important;
            gap: 8px !important;
        }

        .preview-item {
            height: 120px !important;
            border-radius: 8px !important;
        }
        
        /* FAB Container - safely above bottom bar by default, but allow JS to override */
        .fc2-fab-container {
            bottom: 80px; 
            right: 20px;
        }

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

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

        .fc2-enh-settings-tabs {
            scroll-snap-type: x mandatory;
            scrollbar-width: none;
        }

        .fc2-enh-tab-btn {
            scroll-snap-align: start;
        }

        /* Better touch feedback */
        .fc2-fab-trigger,
        .fc2-fab-btn,
        .enh-viewer-action,
        .enh-thumb-item {
            -webkit-tap-highlight-color: rgba(var(--fc2-primary-rgb), 0.2) !important;
        }
    }
`;
  const getConsolidatedCss = () => {
    const C = Config.CLASSES;
    const performanceFix = location.hostname.includes("missav") || location.hostname.includes("supjav") || location.hostname.includes("javdb") ? `
        .${C.processedCard}:nth-child(n+51) { content-visibility: auto; contain-intrinsic-size: 320px 280px; }
    ` : "";
    const siteSpecificFix = location.hostname.includes("fd2ppv") ? `
        .artist-card.card-rebuilt,
        .work-card.card-rebuilt,
        .work-list > div,
        .artist-list > div { overflow: visible !important; }
    ` : "";
    return `
        ${tokens}
        ${animations}
        ${getBaseStyles(C)}
        ${getComponentStyles(C)}
        ${performanceFix}
        ${siteSpecificFix}
        ${getMobileStyles(C)}
    `;
  };
  const log$y = Logger.scope("Style");
  class StyleService {
    constructor() {
      this.themeObserver = null;
    }
    async onInit() {
      log$y.debug("Injecting global styles");
      this.injectCss();
      this.initThemeDetection();
    }
    onCleanup() {
      this.themeObserver?.disconnect();
      this.themeObserver = null;
    }
    injectCss() {
      const css = getConsolidatedCss();
      if (typeof GM_addStyle !== "undefined") {
        GM_addStyle(css);
      } else {
        const style = document.createElement("style");
        style.textContent = css;
        document.head.appendChild(style);
      }
    }
    initThemeDetection() {
      let lastUpdate = 0;
      const detectTheme = () => {
        const now = Date.now();
        if (now - lastUpdate < 500) return;
        lastUpdate = now;
        const bodyBg = window.getComputedStyle(document.body).backgroundColor;
        if (!bodyBg || bodyBg === "rgba(0, 0, 0, 0)" || bodyBg === "transparent") return;
        const rgb = bodyBg.match(/\d+/g);
        if (rgb && rgb.length >= 3) {
          const r = parseInt(rgb[0]), g = parseInt(rgb[1]), b = parseInt(rgb[2]);
          const isLight = r * 0.299 + g * 0.587 + b * 0.114 > 180;
          const hasClass = document.documentElement.classList.contains("fc2-light-theme");
          if (isLight && !hasClass) {
            document.documentElement.classList.add("fc2-light-theme");
          } else if (!isLight && hasClass) {
            document.documentElement.classList.remove("fc2-light-theme");
          }
        }
      };
      if (document.body) detectTheme();
      else document.addEventListener("DOMContentLoaded", detectTheme);
      this.themeObserver = new MutationObserver(() => detectTheme());
      this.themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
      this.themeObserver.observe(document.body, { attributes: true, attributeFilter: ["class", "style"] });
    }
    getCss() {
      return getConsolidatedCss();
    }
  }
  const StyleManager = AppContainer.register("style", new StyleService());
  const log$x = Logger.scope("UIHost");
  class OverlayStackImpl {
    constructor() {
      this.stack = [];
      this.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);
      }
    }
  }
  const OverlayStack = new OverlayStackImpl();
  class UIHostImpl {
    constructor() {
      this.host = null;
      this._shadow = null;
      this.styleSheet = null;
    }
    onInit() {
      if (this.host && this.host.isConnected) return;
      log$x.debug("Initializing shadow host");
      this.host = h("div", {
        id: DOM_IDS.UI_HOST,
        style: {
          position: "fixed",
          inset: "0",
          pointerEvents: "none",
          zIndex: String(UI_CONSTANTS.Z_INDEX_MAX)
        }
      });
      this._shadow = this.host.attachShadow({ mode: "open" });
      const css = StyleManager.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() {
      if (!this._shadow) this.onInit();
      return this._shadow;
    }
    add(el) {
      if (!this._shadow) this.onInit();
      this._shadow.appendChild(el);
    }
  }
  const UIHost = new UIHostImpl();
  const log$w = Logger.scope("Toast");
  const Toast = {
    container: null,
    toasts: new Set(),
    init() {
      if (this.container) return;
      this.container = h("div", { className: "fc2-toast-container" });
      UIHost.add(this.container);
    },
    show(message, type = "info", options = {}) {
      this.init();
      const duration = options.duration === 0 ? 0 : options.duration || TIMING.TOAST_DEFAULT_DURATION;
      const showClose = options.showClose ?? true;
      const iconSvg = type === "success" ? IconCircleCheck : type === "error" ? IconCircleXmark : type === "warn" ? IconTriangleExclamation : IconCircleInfo;
      const colorMap = {
        success: UI_TOKENS.COLORS.SUCCESS,
        error: UI_TOKENS.COLORS.ERROR,
        warn: UI_TOKENS.COLORS.WARN,
        info: UI_TOKENS.COLORS.INFO
      };
      const color = colorMap[type];
      const progress = duration > 0 ? h("div", {
        className: "fc2-toast-progress",
        style: {
          background: color,
          animation: `fc2-toast-shrink ${duration}ms linear forwards`
        }
      }) : null;
      const closeBtn = showClose ? h(
        "button",
        {
          className: "fc2-toast-close",
          "aria-label": "关闭",
          onclick: (e) => {
            e.stopPropagation();
            this.remove(el);
          }
        },
        UIUtils.icon(IconXmark)
      ) : null;
      const iconEl = h("div", { className: "fc2-toast-icon" }, UIUtils.icon(iconSvg));
      const actionBtn = options.action ? h(
        "button",
        {
          className: "fc2-toast-action",
          onclick: (e) => {
            e.stopPropagation();
            options.action?.onClick();
            this.remove(el);
          }
        },
        options.action.label
      ) : null;
      const el = h(
        "div",
        {
          className: `fc2-toast-item toast-${type}`,
          onclick: options.onClick
        },
        iconEl,
        h("span", { className: "fc2-toast-content" }, message),
        actionBtn,
        closeBtn,
        progress
      );
      log$w.trace(`Showing [${type}]: ${message}`);
      this.container.appendChild(el);
      this.toasts.add(el);
      if (duration > 0) {
        setTimeout(() => this.remove(el), duration);
      }
      return el;
    },
    remove(el) {
      if (!this.toasts.has(el)) return;
      el.classList.add("hiding");
      const onEnd = () => {
        log$w.trace("Removing toast element");
        el.remove();
        this.toasts.delete(el);
      };
      el.addEventListener(
        "animationend",
        (e) => {
          if (e.animationName === "fc2-toast-out") onEnd();
        },
        { once: true }
      );
      setTimeout(onEnd, TIMING.UI_TRANSITION_SLOW);
    }
  };
  CoreEvents.on(AppEvents.SHOW_TOAST, (payload) => {
    Toast.show(payload.message, payload.type);
  });
  const log$v = Logger.scope("Retry");
  const _RetryManager = class _RetryManager {
    static async executeWithRetry(operation, config = {}) {
      const finalConfig = { ...this.defaultConfig, ...config };
      let lastError;
      for (let attempt = 0; attempt <= finalConfig.maxRetries; attempt++) {
        try {
          log$v.debug(`Attempt ${attempt + 1}/${finalConfig.maxRetries + 1}`);
          return await operation();
        } catch (error) {
          lastError = error;
          if (attempt === finalConfig.maxRetries) {
            log$v.error("All retries exhausted", error);
            throw error;
          }
          if (finalConfig.shouldRetry && !finalConfig.shouldRetry(error)) {
            log$v.warn("Error not retryable", error);
            throw error;
          }
          finalConfig.onRetry?.(error, attempt + 1);
          const delay = finalConfig.backoffMs[attempt] || finalConfig.backoffMs[finalConfig.backoffMs.length - 1];
          log$v.debug(`Retry in ${delay}ms`, { attempt: attempt + 1, error });
          await new Promise((resolve) => setTimeout(resolve, delay));
        }
      }
      throw lastError;
    }
  };
  _RetryManager.defaultConfig = {
    maxRetries: 3,
    backoffMs: [1e3, 3e3, 5e3],
    shouldRetry: (error) => {
      const err = error;
      const status = err.status;
      return status === 0 || typeof status === "number" && status >= 500 && status < 600;
    }
  };
  _RetryManager.configs = {
    network: {
      maxRetries: 3,
      backoffMs: [1e3, 3e3, 5e3],
      shouldRetry: (error) => {
        const err = error;
        return !!(err.status === 0 || err.status === 408 || err.status && err.status >= 500 && err.status < 600);
      },
      onRetry: (_error, attempt) => {
        Toast.show(`网络错误,正在重试 (${attempt}/3)...`, "warn");
      }
    },
    sync: {
      maxRetries: 2,
      backoffMs: [2e3, 5e3],
      shouldRetry: (error) => {
        const err = error;
        return !!(err.status !== 401 && err.status !== 403);
      },
      onRetry: (_error, attempt) => {
        log$v.debug(`Retrying sync (${attempt}/2)`);
      }
    },
    magnet: {
      maxRetries: 2,
      backoffMs: [1500, 3e3],
      shouldRetry: (error) => {
        const err = error;
        return !!(err.status !== 429);
      }
    }
  };
  let RetryManager = _RetryManager;
  const log$u = Logger.scope("Gzip");
  class GzipServiceImpl {
    constructor() {
      this._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 stream = data instanceof Blob ? data.stream() : new Blob([data], { type: "application/json" }).stream();
        const compressedStream = stream.pipeThrough(new CompressionStream("gzip"));
        return await new Response(compressedStream).blob();
      } catch (error) {
        log$u.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 {
        const isGzip = await this.checkIfGzip(data);
        if (!isGzip) {
          return await data.text();
        }
        const stream = data.stream();
        const decompressedStream = stream.pipeThrough(new DecompressionStream("gzip"));
        return await new Response(decompressedStream).text();
      } catch (error) {
        log$u.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;
    }
  }
  const GzipService = new GzipServiceImpl();
  const log$t = Logger.scope("State");
  class StateService {
    constructor() {
      this.skipBroadcast = false;
      this.saveState = Utils.debounce(async (data) => {
        try {
          const appState = data;
          const SENSITIVE_PROPS = ["supabaseKey", "supabasePassword", "webdavPass"];
          const SENSITIVE_MAP = {
            supabaseKey: "SUPABASE_KEY",
            supabasePassword: "SUPABASE_PASSWORD",
            webdavPass: "WEBDAV_PASS"
          };
          const { syncStatus: _s, supabaseKey: _sk, supabasePassword: _sp, webdavPass: _wp, ...toSave } = appState;
          Storage.set(Config.STORAGE_KEYS.SETTINGS, toSave);
          for (const prop of SENSITIVE_PROPS) {
            const val = appState[prop];
            if (typeof val === "string" && val) {
              const storageKey = SENSITIVE_MAP[prop];
              if (storageKey) await Storage.setEncrypted(STORAGE_KEYS[storageKey], val);
            }
          }
        } catch (e) {
          log$t.error("Failed to save state", e);
        }
      }, TIMING.DEBOUNCE_MS);
      this.ready = new Promise((resolve) => {
        this.resolveReady = resolve;
      });
      this.initProxy();
    }
    getDefaults() {
      return {
        previewMode: "static",
        hideNoMagnet: false,
        hideCensored: false,
        enableHistory: true,
        hideViewed: false,
        enableFollows: false,
        loadExtraPreviews: false,
        enableQuickBar: true,
        showViewedBtn: true,
        showIdBadge: true,
        enableMagnets: true,
        enableExternalLinks: true,
        enableActressName: true,
        hideBlocked: true,
        hideUnwanted: false,
        language: Storage.get("language", "auto") || "auto",
        lastSyncTs: UI_CONSTANTS.DEFAULT_TIMESTAMP,
        supabaseUrl: Storage.get("supabase_url", "") || "",
        supabaseKey: Storage.get("supabase_key", "") || "",
        supabaseEmail: Storage.get("supabase_email", "") || "",
        supabasePassword: Storage.get("supabase_password", "") || "",
        webdavUrl: Storage.get("webdav_url", "") || "",
        webdavUser: Storage.get("webdav_user", "") || "",
        webdavPass: Storage.get("webdav_pass", "") || "",
        webdavPath: Storage.get("webdav_path", UI_CONSTANTS.DEFAULT_SYNC_FILENAME) || UI_CONSTANTS.DEFAULT_SYNC_FILENAME,
        syncMode: "none",
        syncStatus: "idle",
        syncInterval: 5,
        replaceFc2Covers: false,
        enabledPortals: [
          "supjav",
          "missav",
          "javdb",
          "javbus",
          "javlibrary",
          "dmm",
          "fc2",
          "fc2ppvdb",
          "fd2ppv",
          "sukebei"
        ],
        userGridColumns: Number(Storage.get(STORAGE_KEYS.USER_GRID_COLUMNS, 0)) || 0,
        debugMode: Storage.get(STORAGE_KEYS.DEBUG_MODE, false) || false,
        customFolders: Storage.get("custom_folders", []) || [],
        collectionCardWidth: Number(Storage.get("collection_card_width", 200)) || 200
      };
    }
    initProxy() {
      const defaults = this.getDefaults();
      const stored = Storage.get(Config.STORAGE_KEYS.SETTINGS, {}) || {};
      if (stored.syncMode === void 0) {
        const val = Storage.get("sync_mode", "none");
        stored.syncMode = ["none", "supabase", "webdav"].includes(val) ? val : "none";
      }
      const rawState = { ...defaults, ...stored };
      this.proxy = new Proxy(rawState, {
        get: (target, prop) => {
          const value = target[prop];
          if (Array.isArray(value)) return [...value];
          return value;
        },
        set: (target, prop, value) => {
          if (typeof prop === "string" && prop in target) {
            const kProp = prop;
            const currentValue = target[kProp];
            if (currentValue === value) return true;
            if (typeof currentValue === "object" && currentValue !== null && typeof value === "object" && value !== null) {
              if (JSON.stringify(currentValue) === JSON.stringify(value)) return true;
            }
            target[kProp] = value;
            if (kProp !== "syncStatus") {
              this.saveState(target);
              if (!this.skipBroadcast) {
                MessagingService.broadcast(MessageType.SETTING_UPDATE, { prop: kProp, value });
              }
              CoreEvents.emit(AppEvents.STATE_CHANGED, { prop: kProp, value });
              if (kProp === "language") CoreEvents.emit(AppEvents.LANGUAGE_CHANGED, value);
            }
          } else {
            target[prop] = value;
          }
          return true;
        }
      });
    }
    async onInit() {
      log$t.debug("Initializing StateService (secret decryption)");
      this.initCrossTabSync();
      CoreEvents.on(AppEvents.STATE_CHANGED, ({ prop, value }) => {
        if (prop === "debugMode") {
          if (value) Logger.enable(false);
          else Logger.disable(false);
        }
      });
      if (this.proxy.debugMode) {
        Logger.enable(false);
      }
      const SENSITIVE_PROPS = ["supabaseKey", "supabasePassword", "webdavPass"];
      const SENSITIVE_MAP = {
        supabaseKey: "SUPABASE_KEY",
        supabasePassword: "SUPABASE_PASSWORD",
        webdavPass: "WEBDAV_PASS"
      };
      for (const prop of SENSITIVE_PROPS) {
        const storageKey = STORAGE_KEYS[SENSITIVE_MAP[prop]];
        const raw = Storage.get(storageKey, "");
        if (!raw) continue;
        try {
          const decrypted = await CryptoService.decrypt(raw);
          this.proxy[prop] = decrypted;
        } catch {
          log$t.warn(`Migrating plain-text secret: ${String(prop)}`);
          await Storage.setEncrypted(storageKey, raw);
          this.proxy[prop] = raw;
        }
      }
      this.resolveReady();
      log$t.info("StateService ready");
    }
    initCrossTabSync() {
      MessagingService.onMessage((msg) => {
        if (msg.type === MessageType.SETTING_UPDATE) {
          const { prop, value } = msg.payload;
          if (this.proxy[prop] !== value) {
            this.skipBroadcast = true;
            this.proxy[prop] = value;
            this.skipBroadcast = false;
          }
        }
      });
    }
    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);
        }
      });
    }
  }
  const State = AppContainer.register("state", new StateService());
  class GridService {
    constructor() {
      this.styleEl = null;
    }
    onBootstrap() {
      CoreEvents.on(AppEvents.GRID_CHANGED, (cols2) => this.apply(cols2));
      CoreEvents.on(AppEvents.STATE_CHANGED, ({ prop, value }) => {
        if (prop === "userGridColumns") {
          this.apply(Number(value));
        }
      });
      const cols = State.proxy.userGridColumns;
      if (cols > 0) {
        this.apply(cols);
      }
    }
    apply(cols) {
      const hn = location.hostname;
      if (hn.includes("missav")) {
        const path = location.pathname;
        if (/\/(cn\/|en\/|ja\/)?(fc2-ppv-|[a-z]{2,5}-)\d+/i.test(path)) return;
      }
      if (!this.styleEl) {
        this.styleEl = document.createElement("style");
        this.styleEl.id = "fc2-modern-grid-style";
        document.head.appendChild(this.styleEl);
      }
      const sel = hn.includes("fc2ppvdb.com") ? {
        cont: "[data-enh-grid-container], .flex.flex-wrap.-m-4.py-4, .container > .flex.flex-wrap:not(.flex-end):not(.flex-between), #actress-articles .flex.flex-wrap:not(.flex-end):not(.flex-between), section > div > .flex.flex-wrap:not(.flex-end):not(.flex-between)",
        card: `> .${Config.CLASSES.cardRebuilt}`
      } : hn.includes("fd2ppv.cc") ? {
        cont: "#artist-list, .artist-list, #work-list, .work-list, .flex.flex-wrap:not(.flex-end):not(.flex-between), .container .grid, .other-works-grid",
        card: `> .${Config.CLASSES.cardRebuilt}`
      } : hn.includes("supjav.com") ? { cont: ".posts.clearfix:not(:has(.swiper-wrapper))", card: `> .${Config.CLASSES.cardRebuilt}` } : hn.includes("missav") ? { cont: "[data-enh-grid-container]", card: `> .${Config.CLASSES.cardRebuilt}` } : hn.includes("javdb") ? { cont: ".movie-list, .tile-images.tile-small", card: `> .${Config.CLASSES.cardRebuilt}` } : null;
      if (!sel || cols <= 0) {
        this.styleEl.textContent = "";
        return;
      }
      const containerList = sel.cont.split(",").map((s) => s.trim());
      const cardRules = containerList.map((s) => `${s} ${sel.card}`).join(", ");
      const baseGridCss = `
            display: grid !important; 
            grid-template-columns: repeat(${Math.min(cols, 2)}, 1fr) !important; 
            gap: 1rem !important; 
            margin: 0 !important; 
            padding: 1rem 10px !important; 
            width: 100% !important; 
            max-width: none !important; 
            box-sizing: border-box !important;
        `;
      const cardCss = sel.card ? `${cardRules} { padding: 0 !important; margin: 0 !important; width: 100% !important; box-sizing: border-box !important; }` : "";
      this.styleEl.textContent = `
            ${sel.cont} { ${baseGridCss} }
            ${cardCss}
            ${sel.cont} .inner { padding: 0 !important; }
            @media (min-width: 768px) {
                ${sel.cont} { grid-template-columns: repeat(${cols}, 1fr) !important; padding: 1rem 0 !important; }
            }
        `;
    }
  }
  AppContainer.register("grid", new GridService());
  const UIGallery = {
    createExtraPreviewsGrid: (previews) => {
      if (!previews?.length) return null;
      const hero = previews.find((p) => p.type === "image") || previews[0];
      if (!hero) return null;
      const clips = previews.filter((p) => p !== hero);
      return h(
        "div",
        { className: Config.CLASSES.extraPreviewContainer },
        h(
          "div",
          { className: "preview-hero-section" },
          h(
            "div",
            {
              className: "preview-hero-card",
              onclick: () => UIGallery.openGallery("", previews, previews.indexOf(hero))
            },
            hero.type === "image" ? h("img", { src: hero.src, loading: "lazy" }) : h("video", { src: hero.src, autoplay: true, muted: true, loop: true }),
            h("div", { className: "preview-hero-badge" }, t("mainPreview"))
          )
        ),
        clips.length > 0 && h(
          "div",
          { className: Config.CLASSES.extraPreviewGrid },
          ...clips.map((p) => {
            const idx = previews.indexOf(p);
            const isVideo = p.type === "video";
            return h(
              "div",
              {
                className: `preview-item ${isVideo ? "is-video" : ""}`,
                onclick: () => UIGallery.openGallery("", previews, idx)
              },
              isVideo ? h("video", {
                src: p.src,
                muted: true,
                onmouseover: (e) => e.target.play(),
                onmouseout: (e) => {
                  e.target.pause();
                  e.target.currentTime = 0;
                }
              }) : h("img", { src: p.src, loading: "lazy" }),
              isVideo && h("div", { className: "video-play-hint", innerHTML: IconPlayCircle })
            );
          })
        )
      );
    },
    openGallery: (_id, previews, startIndex = 0) => {
      let index = startIndex;
      let isZoomed = false;
      let isTransitioning = false;
      let slideshowInterval = null;
      previews.forEach((item) => {
        if (item.type === "image") {
          const img = new Image();
          img.src = item.src;
        } else if (item.type === "video") {
          const v = document.createElement("video");
          v.preload = "metadata";
          v.src = item.src;
        }
      });
      const container2 = h("div", { className: "enh-viewer-backdrop" });
      const toggleSlideshow = (btn) => {
        if (slideshowInterval) {
          clearInterval(slideshowInterval);
          slideshowInterval = null;
          btn.classList.remove("active");
          btn.textContent = "";
          btn.appendChild(h("span", { innerHTML: IconPlay }));
        } else {
          slideshowInterval = window.setInterval(() => {
            index = (index + 1) % previews.length;
            render("next");
          }, 3e3);
          btn.classList.add("active");
          btn.textContent = "";
          btn.appendChild(h("span", { innerHTML: IconPause }));
        }
      };
      const searchCurrent = () => {
        const item = previews[index];
        if (item && item.type === "image") {
          const url = EXTERNAL_URLS.GOOGLE_LENS.replace("{url}", encodeURIComponent(item.src));
          window.open(url, "_blank");
        }
      };
      const closeBtn = h(
        "div",
        {
          className: "enh-viewer-close",
          onclick: (e) => {
            e.stopPropagation();
            if (slideshowInterval) clearInterval(slideshowInterval);
            container2.remove();
          }
        },
        h("span", { className: "fc2-icon", innerHTML: IconXmark })
      );
      const btnSlideshow = h("button", {
        className: "enh-viewer-action pb-play",
        title: t("gallerySlideshow"),
        onclick: (e) => {
          e.stopPropagation();
          toggleSlideshow(e.currentTarget);
        },
        innerHTML: IconPlay
      });
      const btnSearch = h("button", {
        className: "enh-viewer-action pb-search",
        title: t("gallerySearch"),
        onclick: (e) => {
          e.stopPropagation();
          searchCurrent();
        },
        innerHTML: IconImageSearch
      });
      const actionBar = h("div", { className: "enh-viewer-actions" }, btnSlideshow, btnSearch);
      const counterCurrent = h("span", { style: { color: "#fff", fontWeight: "bold" } }, "1");
      const counterTotal = h("span", {}, previews.length.toString());
      const counter = h(
        "div",
        { className: "enh-viewer-counter" },
        counterCurrent,
        h("span", { style: { opacity: "0.5", margin: "0 4px" } }, "/"),
        counterTotal
      );
      const stage = h("div", {
        className: "enh-viewer-stage",
        onclick: (e) => {
          if (e.target === stage) {
            if (slideshowInterval) clearInterval(slideshowInterval);
            container2.remove();
          }
        }
      });
      const thumbStrip = h(
        "div",
        { className: "enh-viewer-thumbs" },
        ...previews.map(
          (p, idx) => h(
            "div",
            {
              className: `enh-thumb-item ${idx === index ? "active" : ""}`,
              "data-idx": idx,
              onclick: (e) => {
                e.stopPropagation();
                if (idx === index) return;
                isZoomed = false;
                index = idx;
                render("init");
              }
            },
            p.type === "image" ? h("img", { src: p.src }) : h("video", { src: p.src, muted: true })
          )
        )
      );
      if (previews.length > 1) {
        const navPrev = h(
          "div",
          {
            className: "enh-viewer-nav prev",
            onclick: (e) => {
              e.stopPropagation();
              isZoomed = false;
              index = (index - 1 + previews.length) % previews.length;
              render("prev");
            }
          },
          h("span", { className: "fc2-icon", style: { transform: "scale(1.5)" }, innerHTML: IconChevronLeft })
        );
        const navNext = h(
          "div",
          {
            className: "enh-viewer-nav next",
            onclick: (e) => {
              e.stopPropagation();
              isZoomed = false;
              index = (index + 1) % previews.length;
              render("next");
            }
          },
          h("span", {
            className: "fc2-icon",
            style: { transform: "scale(1.5)" },
            innerHTML: IconChevronRight
          })
        );
        container2.append(navPrev, navNext);
      }
      container2.append(stage, closeBtn, actionBar, counter, thumbStrip);
      const render = (direction = "init") => {
        if (isTransitioning) return;
        isTransitioning = true;
        const item = previews[index];
        if (!item) {
          isTransitioning = false;
          return;
        }
        counterCurrent.textContent = (index + 1).toString();
        if (item.type === "image") {
          btnSearch.style.display = "flex";
        } else {
          btnSearch.style.display = "none";
        }
        thumbStrip.querySelectorAll(".enh-thumb-item").forEach((el) => el.classList.remove("active"));
        const activeThumb = thumbStrip.querySelector(`.enh-thumb-item[data-idx="${index}"]`);
        if (activeThumb) {
          activeThumb.classList.add("active");
          activeThumb.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" });
        }
        stage.className = `enh-viewer-stage slide-${direction}`;
        stage.style.cssText = isZoomed ? "overflow: auto; align-items: flex-start; justify-content: flex-start;" : "";
        stage.textContent = "";
        const media = item.type === "image" ? h("img", {
          src: item.src,
          draggable: false,
          onclick: (e) => {
            e.stopPropagation();
            isZoomed = !isZoomed;
            render("init");
          },
          style: isZoomed ? "cursor: zoom-out; max-width: none; max-height: none; width: auto; height: auto; margin: auto; transform: scale(1);" : "cursor: zoom-in; width: 100%; height: 100%; object-fit: contain;"
        }) : h("video", {
          src: item.src,
          controls: true,
          autoplay: true,
          loop: true,
          onclick: (e) => e.stopPropagation(),
          style: "width: 100%; height: 100%; object-fit: contain; border-radius: 8px;"
        });
        stage.appendChild(media);
        setTimeout(() => {
          isTransitioning = false;
        }, TIMING.UI_TRANSITION_NORMAL);
      };
      const keyHandler = ((e) => {
        if (!container2.isConnected) {
          window.removeEventListener("keydown", keyHandler);
          if (slideshowInterval) clearInterval(slideshowInterval);
          return;
        }
        const key = e.key.toLowerCase();
        if (key === "a" || key === "arrowleft") {
          e.stopPropagation();
          isZoomed = false;
          index = (index - 1 + previews.length) % previews.length;
          render("prev");
        } else if (key === "d" || key === "arrowright") {
          e.stopPropagation();
          isZoomed = false;
          index = (index + 1) % previews.length;
          render("next");
        } else if (key === "escape") {
          e.stopPropagation();
          if (slideshowInterval) clearInterval(slideshowInterval);
          container2.remove();
        }
      });
      window.addEventListener("keydown", keyHandler);
      let touchStartX = 0;
      let touchStartY = 0;
      let lastTapTime = 0;
      container2.addEventListener(
        "touchstart",
        (e) => {
          const touch = e.changedTouches[0];
          if (touch) {
            touchStartX = touch.screenX;
            touchStartY = touch.screenY;
          }
        },
        { passive: true }
      );
      container2.addEventListener(
        "touchend",
        (e) => {
          const touch = e.changedTouches[0];
          if (!touch) return;
          const touchEndX = touch.screenX;
          const touchEndY = touch.screenY;
          const diffX = touchEndX - touchStartX;
          const diffY = touchEndY - touchStartY;
          const now = Date.now();
          const item = previews[index];
          if (item && now - lastTapTime < TIMING.UI_TRANSITION_NORMAL && Math.abs(diffX) < 10 && Math.abs(diffY) < 10) {
            if (item.type === "image") {
              e.preventDefault();
              isZoomed = !isZoomed;
              render("init");
            }
          }
          lastTapTime = now;
          if (!isZoomed) {
            if (diffY > UI_CONSTANTS.SWIPE_DISMISS_THRESHOLD && Math.abs(diffX) < 50) {
              if (slideshowInterval) clearInterval(slideshowInterval);
              container2.remove();
              return;
            }
            if (Math.abs(diffX) > 50 && Math.abs(diffY) < 50) {
              if (diffX > 0) {
                index = (index - 1 + previews.length) % previews.length;
                render("prev");
              } else {
                index = (index + 1) % previews.length;
                render("next");
              }
            }
          }
        },
        { passive: false }
      );
      setTimeout(() => {
        const activeThumb = thumbStrip.querySelector(".enh-thumb-item.active");
        if (activeThumb) activeThumb.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" });
      }, 50);
      render();
      UIHost.add(container2);
    }
  };
  const log$s = Logger.scope("ScraperQueue");
  class ScraperQueue {
    constructor(maxConcurrent, scope) {
      this.maxConcurrent = maxConcurrent;
      this.scope = scope;
      this.queue = [];
      this.activeRequests = 0;
    }
    getBackoff() {
      return Number(Storage.get(`scraper_backoff_${this.scope}_until`, 0));
    }
    getBackoffLevel() {
      return Number(Storage.get(`scraper_backoff_${this.scope}_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 jitter = Math.min(3e3, finalDelay * 0.1) * Math.random();
      const totalDuration = finalDelay + jitter;
      const until = Date.now() + totalDuration;
      const nextLevel = Math.min(currentLevel + 1, 6);
      Storage.set(`scraper_backoff_${this.scope}_until`, until);
      Storage.set(`scraper_backoff_${this.scope}_level`, nextLevel);
      log$s.warn(`[${this.scope}] Backoff triggered by ${source} for ${Math.ceil(totalDuration / 1e3)}s (L${nextLevel})`);
    }
    resetBackoff() {
      if (this.getBackoffLevel() > 0) {
        Storage.set(`scraper_backoff_${this.scope}_level`, 0);
        log$s.debug(`[${this.scope}] Backoff level reset`);
      }
    }
    resetFullBackoff() {
      Storage.delete(`scraper_backoff_${this.scope}_until`);
      Storage.delete(`scraper_backoff_${this.scope}_level`);
      log$s.debug(`[${this.scope}] Full backoff reset`);
    }
    async checkBackoff() {
      const backoffUntil = this.getBackoff();
      const now = Date.now();
      if (now < backoffUntil) {
        return false;
      }
      return true;
    }
    async waitBackoff() {
      const backoffUntil = this.getBackoff();
      const now = Date.now();
      if (now < backoffUntil) {
        await Utils.sleep(backoffUntil - now);
      }
    }
    enqueue(task) {
      this.queue.push(task);
      this.processQueue();
    }
    async processQueue() {
      if (this.activeRequests >= this.maxConcurrent || this.queue.length === 0) return;
      this.activeRequests++;
      const task = this.queue.shift();
      try {
        await task();
      } finally {
        this.activeRequests--;
        void this.processQueue();
      }
    }
  }
  const log$r = Logger.scope("MagnetProvider");
  class MagnetProvider {
    constructor(sukebeiQueue) {
      this.sukebeiQueue = sukebeiQueue;
    }
    async fetchFromSukebei(chunk, type, onResult, traceId) {
      const query = type === "fc2" ? chunk.join("|") : chunk.flatMap((id) => [id, id.replace(/-/g, "_")]).join("|");
      const url = Config.SCRAPER_URLS.SUKEBEI_SEARCH.replace("{query}", encodeURIComponent(query));
      try {
        const rawHtml = await http(url, { type: "text" });
        const doc = new DOMParser().parseFromString(rawHtml, "text/html");
        if (doc.title && (doc.title.includes("Cloudflare") || doc.title.includes("Attention Required"))) {
          log$r.warn("Cloudflare blocked, backing off 60s", void 0, traceId);
          this.sukebeiQueue.triggerBackoff(TIMING.CLOUDFLARE_BACKOFF_MS, "Sukebei (Cloudflare)");
          return new Set();
        }
        const rows = doc.querySelectorAll("table.torrent-list tbody tr");
        const foundIds = new Set();
        const regexes = chunk.map((id) => {
          const escaped = id.toUpperCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
          return {
            id,
            regex: new RegExp(`(?<=[^0-9a-zA-Z]|^)${escaped.replace(/[-_\s]/g, "[-_\\s]")}(?=[^0-9a-zA-Z]|$)`, "i"),
            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 } of regexes) {
              if (foundIds.has(id)) continue;
              const match = title.match(regex);
              if (match) {
                const idx = match.index;
                const matchedStr = match[0];
                if (!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$r.info(`Sukebei: ${foundIds.size}/${chunk.length} found`, void 0, traceId);
        this.sukebeiQueue.resetBackoff();
        return foundIds;
      } catch (e) {
        const err = e;
        if (err.status === 429) {
          this.sukebeiQueue.triggerBackoff(TIMING.RATE_LIMIT_BACKOFF_MS, "Sukebei (429)");
        } else {
          log$r.error("Sukebei fetch error", e, traceId);
        }
        return new Set();
      }
    }
    async fetchFrom0cili(id, type, onResult, traceId) {
      try {
        const searchQuery = type === "fc2" ? id : id.replace(/-/g, " ");
        const url = Config.SCRAPER_URLS.OCILI_SEARCH.replace("{query}", encodeURIComponent(searchQuery));
        const rawHtml = await http(url, { type: "text" });
        const doc = new DOMParser().parseFromString(rawHtml, "text/html");
        const rows = doc.querySelectorAll("table tbody tr, .torrent-list tr");
        let found = false;
        const searchId = id.toUpperCase();
        const regex = new RegExp(`(?<=[^0-9a-zA-Z]|^)${searchId.replace(/[-_\s]/g, "[-_\\s]")}(?=[^0-9a-zA-Z]|$)`, "i");
        for (const row of Array.from(rows)) {
          const magnetAnchor = row.querySelector("a[href^='magnet:?']");
          const titleElement = row.querySelector("a[title], td:nth-child(2) a, .torrent-name a");
          if (magnetAnchor && titleElement) {
            const title = (titleElement.getAttribute("title") || titleElement.textContent || "").trim().toUpperCase();
            if (regex.test(title)) {
              onResult(id, magnetAnchor.href);
              found = true;
              log$r.info(`0cili: Found magnet for ${id}`, void 0, traceId);
              break;
            }
          }
        }
        if (!found) {
          onResult(id, null);
          log$r.debug(`0cili: No magnet for ${id}`, void 0, traceId);
        }
        await Utils.sleep(TIMING.OCILI_DELAY_MS + Math.random() * TIMING.OCILI_RANDOM_DELAY_MS);
      } catch (e) {
        log$r.error(`0cili fetch error for ${id}`, e, traceId);
        onResult(id, null);
      }
    }
  }
  const log$q = Logger.scope("PreviewProvider");
  class PreviewProvider {
    constructor(wumaobiQueue) {
      this.wumaobiQueue = wumaobiQueue;
    }
    async fetchExtraPreviews(fc2Id) {
      try {
        const normalizedId = fc2Id.padStart(7, "0");
        const idPattern = `${PATTERNS.FC2_PPV_PREFIX}${normalizedId}`;
        const url = Config.SCRAPER_URLS.WUMAOBI_DETAIL.replace("{id}", normalizedId);
        const rawHtml = await http(url, { type: "text" });
        const doc = new DOMParser().parseFromString(rawHtml, "text/html");
        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.WUMAOBI_BASE + src;
              results.push({ type: "image", src });
            }
          }
        });
        doc.querySelectorAll("video").forEach((video) => {
          let src = video.getAttribute("src") || video.querySelector("source")?.getAttribute("src");
          if (src) {
            const isRelevant = src.includes(idPattern) || src.includes(fc2Id);
            if (isRelevant) {
              if (src.startsWith("/")) src = Config.SCRAPER_URLS.WUMAOBI_BASE + src;
              results.push({ type: "video", src });
            }
          }
        });
        return results;
      } catch (err) {
        const e = err;
        if (e.status === 404) {
          log$q.debug(`No previews found on Wumaobi for ${fc2Id}`);
        } else if (e.status === 429 || e.status === 503) {
          this.wumaobiQueue.triggerBackoff(TIMING.RATE_LIMIT_BACKOFF_MS, `Wumaobi (${e.status})`);
        } else {
          log$q.warn(`Failed to fetch extra previews: ${fc2Id} (Status: ${e.status || "Unknown"})`);
        }
        return [];
      }
    }
  }
  const log$p = Logger.scope("ActressProvider");
  class ActressProvider {
    async fetchActressFromFD2(id) {
      const cacheKey = `actress_${id}`;
      const cached = await Repository.cache.get(cacheKey);
      if (cached) {
        const cleanCached = MediaUtils.cleanActressName(cached);
        if (cleanCached) {
          log$p.debug(`Actress cache hit: ${id} = ${cleanCached}`);
          return cleanCached;
        } else {
          log$p.warn(`Removing invalid cached actress for ${id}: ${cached}`);
          await Repository.cache.delete(cacheKey);
        }
      }
      const backoffEnd = Number(Storage.get("fd2_backoff_until", 0));
      if (Date.now() < backoffEnd) {
        log$p.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("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("last_actress_fetch", Date.now());
      try {
        const url = EXTERNAL_URLS.FD2PPV.replace("{id}", id);
        const html = await http(url, { type: "text" });
        const doc = new DOMParser().parseFromString(html, "text/html");
        if (doc.title && CLOUDFLARE_INDICATORS.some((s) => doc.title.includes(s))) {
          log$p.warn("FD2PPV Cloudflare detected, backing off 5m");
          Storage.set("fd2_backoff_until", Date.now() + TIMING.FD2_BACKOFF_MS);
          return null;
        }
        const actressLinks = Array.from(doc.querySelectorAll(".artist-info-card .artist-name a"));
        let actress = null;
        for (const link of actressLinks) {
          const cleaned = MediaUtils.cleanActressName(link.textContent);
          if (cleaned) {
            actress = cleaned;
            break;
          }
        }
        if (actress) {
          await Repository.cache.set(cacheKey, actress);
          log$p.info(`Fetched actress: ${id} = ${actress}`);
        }
        return actress;
      } catch (e) {
        const err = e;
        if (err.status === 403 || err.status === 429 || err.status === 503) {
          log$p.warn(`FD2PPV error (${err.status}), backing off 5m`);
          Storage.set("fd2_backoff_until", Date.now() + TIMING.FD2_BACKOFF_MS);
        } else {
          log$p.error(`Failed to fetch actress: ${id}`, e);
        }
        return null;
      }
    }
    resetBackoff() {
      Storage.delete("fd2_backoff_until");
      log$p.debug("FD2PPV backoff reset");
    }
  }
  const log$o = Logger.scope("Scraper");
  class ScraperServiceImplementation {
    constructor() {
      this.MAX_MAGNET_CONCURRENT = 3;
      this.MAX_PREVIEW_CONCURRENT = 2;
      this.magnetQueue = new ScraperQueue(this.MAX_MAGNET_CONCURRENT, "sukebei");
      this.previewQueue = new ScraperQueue(this.MAX_PREVIEW_CONCURRENT, "wumaobi");
      this.magnetProvider = new MagnetProvider(this.magnetQueue);
      this.previewProvider = new PreviewProvider(this.previewQueue);
      this.actressProvider = new ActressProvider();
    }
    onBootstrap() {
      log$o.debug("Scraper service bootstrapped, queues ready");
    }
    async fetchMagnets(items, onResult) {
      if (!items?.length) return;
      const traceId = Logger.traceId;
      log$o.info(`Starting magnet fetch for ${items.length} items`, { ids: items.map((i) => i.id) }, traceId);
      const grouped = new Map();
      items.forEach((item) => {
        const key = item.type || "fc2";
        if (!grouped.has(key)) grouped.set(key, []);
        grouped.get(key).push(item);
      });
      const pendingPromises = [];
      for (const [type, itemList] of grouped) {
        const ids = itemList.map((i) => i.id);
        for (const chunk of Utils.chunk(ids, NETWORK.CHUNK_SIZE)) {
          pendingPromises.push(
            new Promise((resolve) => {
              this.magnetQueue.enqueue(async () => {
                await this.magnetQueue.waitBackoff();
                const delay = TIMING.MAGNET_BASE_DELAY_MS + Math.random() * TIMING.MAGNET_RANDOM_DELAY_MS;
                await Utils.sleep(delay);
                const foundIds = await this.magnetProvider.fetchFromSukebei(chunk, type, onResult, traceId);
                const failedIds = chunk.filter((id) => !foundIds.has(id));
                if (failedIds.length > 0) {
                  await Promise.all(
                    failedIds.map(
                      (id) => this.magnetProvider.fetchFrom0cili(id, type, onResult, traceId).catch(() => {
                      })
                    )
                  );
                }
                resolve();
              });
            })
          );
        }
      }
      await Promise.all(pendingPromises);
    }
    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 = `has_previews_${fc2Id}`;
      const cached = await Repository.cache.get(cacheKey);
      if (cached !== null) return cached === true;
      return new Promise((resolve) => {
        this.previewQueue.enqueue(async () => {
          const canProceed = await this.previewQueue.checkBackoff();
          if (!canProceed) {
            resolve(false);
            return;
          }
          const jitter = Math.random() * 500;
          await Utils.sleep(TIMING.POLITE_DELAY_MS + jitter);
          try {
            const results = await this.fetchExtraPreviews(fc2Id);
            const exists = results.length > 0;
            if (exists) this.previewQueue.resetBackoff();
            await Repository.cache.set(cacheKey, exists);
            resolve(exists);
          } catch {
            resolve(false);
          }
        });
      });
    }
  }
  const ScraperService = AppContainer.register("scraper-service", new ScraperServiceImplementation());
  const Button$1 = (iconSvg, tip, href, onClick, className = "") => {
    const children = [];
    if (iconSvg) {
      children.push(UIUtils.icon(iconSvg));
    }
    children.push(
      h("span", { className: Config.CLASSES.buttonText }, tip),
      h("span", { className: Config.CLASSES.tooltip }, tip)
    );
    const b = h(
      "a",
      {
        href: href || "javascript:void(0);",
        className: `${Config.CLASSES.resourceBtn} ${className}`.trim(),
        role: "button",
        tabIndex: 0,
        onclick: (e) => {
          if (onClick) {
            e.preventDefault();
            e.stopPropagation();
            onClick(e);
          }
        },
        onkeydown: (e) => {
          if (e.key === "Enter" || e.key === " ") {
            if (onClick) {
              e.preventDefault();
              e.stopPropagation();
              onClick(e);
            }
          }
        }
      },
      ...children
    );
    return b;
  };
  const MagnetButton = (cont, url) => {
    if (cont && !cont.querySelector(`.${Config.CLASSES.btnMagnet}`)) {
      const btn = Button$1(IconMagnet, t("tooltipCopyMagnet"), "javascript:void(0);", (e) => {
        e.preventDefault();
        UIUtils.copyButtonBehavior(btn, url, t("tooltipCopied"));
      });
      btn.classList.add(Config.CLASSES.btnMagnet);
      cont.appendChild(btn);
      const card2 = cont.closest(`.${Config.CLASSES.cardRebuilt}`);
      if (card2) {
        UIUtils.applyCardVisibility(card2, true);
      }
    }
  };
  var PageContext = ((PageContext2) => {
    PageContext2["Unknown"] = "unknown";
    PageContext2["List"] = "list";
    PageContext2["Detail"] = "detail";
    PageContext2["User"] = "user";
    PageContext2["Search"] = "search";
    return PageContext2;
  })(PageContext || {});
  const SYSTEM_FOLDERS = ["wanted", "viewed", "follow"];
  const log$n = Logger.scope("Registry");
  class Registry {
    constructor() {
      this.components = new Map();
      this.elementToComponents = new Map();
      this.observer = null;
      this.initGC();
    }
    initGC() {
      if (typeof MutationObserver === "undefined") return;
      this.observer = new MutationObserver((mutations) => {
        mutations.forEach((m) => {
          if (m.removedNodes.length > 0) {
            m.removedNodes.forEach((node) => {
              if (node instanceof HTMLElement) {
                this.gc(node);
              }
            });
          }
        });
      });
      setTimeout(() => {
        this.observer?.observe(document.body, { childList: true, subtree: true });
      }, 2e3);
    }
    gc(root) {
      this.elementToComponents.forEach((set, el) => {
        if (root === el || root.contains(el)) {
          Array.from(set).forEach((ins) => {
            Logger.trace("Registry", `Auto-GC: detached ${ins.id}`);
            this.unregister(ins);
          });
        }
      });
    }
    register(component) {
      if (!this.components.has(component.id)) {
        this.components.set(component.id, new Set());
      }
      this.components.get(component.id).add(component);
      if (component.element) {
        if (!this.elementToComponents.has(component.element)) {
          this.elementToComponents.set(component.element, new Set());
        }
        this.elementToComponents.get(component.element).add(component);
      }
      Logger.trace("Registry", `Registered: ${component.id}`);
    }
    unregister(component) {
      const set = this.components.get(component.id);
      if (set) {
        set.delete(component);
        if (set.size === 0) this.components.delete(component.id);
      }
      if (component.element) {
        const elSet = this.elementToComponents.get(component.element);
        if (elSet) {
          elSet.delete(component);
          if (elSet.size === 0) this.elementToComponents.delete(component.element);
        }
      }
      if (component.destroy) {
        try {
          component.destroy();
        } catch (err) {
          log$n.warn(`Failed to destroy component ${component.id}`, err);
        }
        delete component.destroy;
      }
    }
    getInstances(id) {
      const set = this.components.get(id);
      return set ? Array.from(set) : [];
    }
    notify(id, data) {
      const instances = this.getInstances(id);
      instances.forEach((ins) => {
        try {
          ins.update(data);
        } catch (e) {
          log$n.error(`Notify failed for ${id}`, e);
        }
      });
    }
    clear() {
      this.components.forEach((set) => {
        set.forEach((ins) => this.unregister(ins));
      });
      this.components.clear();
      this.elementToComponents.clear();
      log$n.debug("Component registry cleared");
    }
  }
  const ComponentRegistry = new Registry();
  const log$m = Logger.scope("ViewStore");
  class ViewStoreImpl {
    constructor() {
      this.states = new Map();
    }
    getDefaults() {
      return {
        isSearching: false,
        hasMagnet: false,
        hasPreviews: false
      };
    }
get(id) {
      if (!this.states.has(id)) {
        this.states.set(id, this.getDefaults());
      }
      return this.states.get(id);
    }
set(id, key, value) {
      const current = this.get(id);
      if (current[key] === value) return;
      current[key] = value;
      log$m.trace(`[${id}] ${String(key)} = ${value}`);
      CoreEvents.emit(AppEvents.VIEW_STATE_CHANGED, { id, key: String(key), value });
      ComponentRegistry.notify(id, { key, value });
    }
update(id, updates) {
      Object.entries(updates).forEach(([key, value]) => {
        this.set(id, key, value);
      });
    }
clear() {
      this.states.clear();
      log$m.debug("All view states cleared");
    }
  }
  const ViewStore = new ViewStoreImpl();
  class UndoManager {
    constructor(maxSize = 30) {
      this.stack = [];
      this.maxSize = maxSize;
    }
    push(action) {
      this.stack.push({ ...action, timestamp: Date.now() });
      if (this.stack.length > this.maxSize) {
        this.stack.shift();
      }
    }
    pop() {
      return this.stack.pop();
    }
    peek() {
      return this.stack[this.stack.length - 1];
    }
    canUndo() {
      return this.stack.length > 0;
    }
    getLabel() {
      const last = this.peek();
      return last?.label ?? null;
    }
    clear() {
      this.stack = [];
    }
  }
  const log$l = Logger.scope("CollectionQuery");
  class CollectionQueryService {
    async getCollectionItems(filter, sort) {
      try {
        let items = await Repository.details.getAll();
        if (filter) {
          items = this.applyFilter(items, filter);
        }
        if (sort) {
          items = this.applySort(items, sort);
        }
        return items;
      } catch (e) {
        log$l.error("Failed to fetch collection items", e);
        return [];
      }
    }
    async getCollectionItemsByFolder(folder) {
      if (!folder) return this.getCollectionItems();
      return this.getCollectionItems({ folder });
    }
    applyFilter(items, filter) {
      return items.filter((item) => {
        if (filter.folder && filter.folder !== "all") {
          if ((item.folder || "wanted") !== filter.folder) return false;
        }
        if (filter.site && filter.site !== "all") {
          if ((item.type || "fc2").toLowerCase() !== filter.site.toLowerCase()) return false;
        }
        if (filter.hasRating && !item.rating) return false;
        if (filter.hasNotes && !item.notes) return false;
        if (filter.hasTags && (!item.userTags || item.userTags.length === 0)) return false;
        if (filter.minRating && (item.rating || 0) < filter.minRating) return false;
        if (filter.tags && filter.tags.length > 0) {
          const itemTags = new Set(item.userTags || []);
          if (!filter.tags.some((t2) => itemTags.has(t2))) return false;
        }
        return true;
      });
    }
    applySort(items, sort) {
      const sorted = [...items];
      switch (sort) {
        case "date-desc":
          return sorted.sort((a, b) => (b.lastAccessed || 0) - (a.lastAccessed || 0));
        case "date-asc":
          return sorted.sort((a, b) => (a.lastAccessed || 0) - (b.lastAccessed || 0));
        case "title":
          return sorted.sort((a, b) => (a.title || "").localeCompare(b.title || ""));
        case "site":
          return sorted.sort((a, b) => (a.type || "").localeCompare(b.type || ""));
        case "rating":
          return sorted.sort((a, b) => (b.rating || 0) - (a.rating || 0));
        case "notes":
          return sorted.sort((a, b) => {
            const aN = a.notes ? 1 : 0;
            const bN = b.notes ? 1 : 0;
            return bN - aN || (b.lastAccessed || 0) - (a.lastAccessed || 0);
          });
        case "folder":
          return sorted.sort((a, b) => (a.folder || "wanted").localeCompare(b.folder || "wanted"));
        default:
          return sorted;
      }
    }
    async getStats() {
      const items = await Repository.details.getAll();
      const folderCounts = {};
      const tagCounts = {};
      const siteDistribution = {};
      let ratedCount = 0;
      let totalRating = 0;
      let withNotes = 0;
      let withTags = 0;
      let oldestItem = Infinity;
      let newestItem = 0;
      for (const item of items) {
        const folder = item.folder || "wanted";
        folderCounts[folder] = (folderCounts[folder] || 0) + 1;
        if (item.userTags && item.userTags.length > 0) {
          withTags++;
          for (const tag of item.userTags) {
            tagCounts[tag] = (tagCounts[tag] || 0) + 1;
          }
        }
        if (item.rating && item.rating > 0) {
          ratedCount++;
          totalRating += item.rating;
        }
        if (item.notes) withNotes++;
        const site = (item.type || "fc2").toLowerCase();
        siteDistribution[site] = (siteDistribution[site] || 0) + 1;
        const ts = item.addedAt || item.lastAccessed || 0;
        if (ts < oldestItem) oldestItem = ts;
        if (ts > newestItem) newestItem = ts;
      }
      return {
        totalItems: items.length,
        folderCounts,
        tagCounts,
        averageRating: ratedCount > 0 ? totalRating / ratedCount : 0,
        ratedCount,
        withNotes,
        withTags,
        siteDistribution,
        oldestItem: oldestItem === Infinity ? 0 : oldestItem,
        newestItem
      };
    }
    async getAllTags() {
      const stats = await this.getStats();
      return Object.entries(stats.tagCounts).map(([tag, count]) => ({ tag, count })).sort((a, b) => b.count - a.count);
    }
    async getFolders() {
      const items = await Repository.details.getAll();
      const folders = new Set(SYSTEM_FOLDERS);
      items.forEach((i) => {
        if (i.folder) folders.add(i.folder);
      });
      return Array.from(folders).sort((a, b) => {
        const aIsSystem = SYSTEM_FOLDERS.includes(a) ? 0 : 1;
        const bIsSystem = SYSTEM_FOLDERS.includes(b) ? 0 : 1;
        if (aIsSystem !== bIsSystem) return aIsSystem - bIsSystem;
        return a.localeCompare(b);
      });
    }
    async getFolderCounts() {
      const items = await Repository.details.getAll();
      const counts = {};
      for (const folder of SYSTEM_FOLDERS) {
        counts[folder] = 0;
      }
      items.forEach((item) => {
        const f = item.folder || "wanted";
        counts[f] = (counts[f] || 0) + 1;
      });
      return counts;
    }
    async findDuplicates() {
      const items = await this.getCollectionItems();
      const groups = new Map();
      items.forEach((item) => {
        let key = item.id.toLowerCase();
        const fc2Match = item.id.match(/\d{5,8}/);
        if (fc2Match) key = `fc2-${fc2Match[0]}`;
        if (!groups.has(key)) groups.set(key, []);
        groups.get(key).push(item);
      });
      return Array.from(groups.values()).filter((g) => g.length > 1);
    }
  }
  const log$k = Logger.scope("CollectionHealth");
  class CollectionHealthService {
    constructor() {
      this.isCheckingHealth = false;
      this.HEALTH_CHECK_CONCURRENCY = 5;
      this.STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1e3;
    }
async runHealthCheck(force = false) {
      if (this.isCheckingHealth) return { checked: 0, repaired: 0 };
      this.isCheckingHealth = true;
      log$k.debug("Starting health check");
      let totalChecked = 0;
      let repairedCount = 0;
      try {
        const items = await Repository.details.getAll();
        const now = Date.now();
        const toCheck = items.filter((item) => force || !item.lastCheck || now - item.lastCheck > this.STALE_THRESHOLD_MS);
        const total = toCheck.length;
        log$k.debug(`Checking ${total} items for health`);
        let processed = 0;
        const pool = new Set();
        for (const item of toCheck) {
          if (pool.size >= this.HEALTH_CHECK_CONCURRENCY) {
            await Promise.race(pool);
          }
          const promise = (async () => {
            const isOk = await this.verifyImageUrl(item.primaryImageUrl || item.imageUrl);
            if (!isOk) {
              const repaired = await this.repairCover(item);
              if (repaired) repairedCount++;
            } else {
              await Repository.details.batchUpdate([item.id], { lastCheck: now });
            }
            processed++;
            CoreEvents.emit(AppEvents.COLLECTION_HEALTH_PROGRESS, {
              processed,
              total,
              repairedCount
            });
          })();
          pool.add(promise);
          promise.finally(() => pool.delete(promise));
        }
        await Promise.all(pool);
        totalChecked = total;
        if (repairedCount > 0) {
          log$k.info(`Health check complete, repaired ${repairedCount}/${total} covers`);
        } else {
          log$k.info(`Health check complete, all ${total} items healthy`);
        }
      } catch (e) {
        log$k.error("Health check failed", e);
      } finally {
        this.isCheckingHealth = false;
      }
      return { checked: totalChecked, repaired: repairedCount };
    }
    async verifyImageUrl(url) {
      if (!url || url.includes("placehold.co")) return false;
      try {
        return new Promise((resolve) => {
          GM_xmlhttpRequest({
            method: "HEAD",
            url,
            timeout: 5e3,
            onload: (res) => resolve(res.status >= 200 && res.status < 400),
            onerror: () => resolve(false),
            ontimeout: () => resolve(false)
          });
        });
      } catch {
        return false;
      }
    }
    async repairCover(item) {
      const id = item.id;
      const mirrors = [
        EXTERNAL_URLS.WUMAOBI_COVER.replace("{id}", id),
        EXTERNAL_URLS.WUMAOBI_COVER.replace("{id}", id).replace(PATTERNS.WUMAOBI_COVER, PATTERNS.WUMAOBI_MAIN),
        EXTERNAL_URLS.FOURHOI_COVER.replace("{id}", id.padStart(7, "0"))
      ];
      for (const mirror of mirrors) {
        if (mirror === (item.primaryImageUrl || item.imageUrl)) continue;
        const isOk = await this.verifyImageUrl(mirror);
        if (isOk) {
          await Repository.details.batchUpdate([id], {
            primaryImageUrl: mirror,
            lastCheck: Date.now()
          });
          await Repository.history.markDirty(id);
          log$k.debug(`Repaired cover for ${id}`);
          return true;
        }
      }
      await Repository.details.batchUpdate([id], { lastCheck: Date.now() });
      return false;
    }
  }
  const log$j = Logger.scope("Collection");
  const COLLECTION_EXPORT_VERSION = 1;
  class CollectionServiceImplementation {
    constructor() {
      this.collectionCache = new Set();
      this.queryService = new CollectionQueryService();
      this.healthService = new CollectionHealthService();
      this.undoManager = new UndoManager();
    }
    onInit() {
      CoreEvents.on(AppEvents.HISTORY_LOADED, () => this.syncFromHistory());
    }
    onBootstrap() {
      log$j.debug("Bootstrapping cache");
      this.syncWithCollection();
    }
async syncWithCollection() {
      Logger.time("CollectionService.sync");
      this.collectionCache.clear();
      try {
        const items = await Repository.details.getAll();
        for (const item of items) {
          this.collectionCache.add(item.id);
        }
      } catch (e) {
        log$j.error("Failed to sync collection cache", e);
      }
      Logger.timeEnd("CollectionService.sync");
      log$j.info(`Warmed up with ${this.collectionCache.size} collected items`);
    }
    syncFromHistory() {
      const cache = HistoryService.getCache();
      for (const [id, status] of cache) {
        if (status === "wanted" && !this.collectionCache.has(id)) {
          this.collectionCache.add(id);
        }
      }
    }
    normalizeId(id) {
      return IdNormalizer.normalize(id);
    }
    folderToStatus(folder) {
      const VALID_STATUSES = new Set(["watched", "wanted", "downloaded", "blocked"]);
      return VALID_STATUSES.has(folder) ? folder : "wanted";
    }
    notifyUI(id, isWanted) {
      ViewStore.set(id, "isWanted", isWanted);
    }
    emitUpdate(id, type) {
      CoreEvents.emit(AppEvents.COLLECTION_UPDATED, { id, type });
    }
async add(id, metadata, folder = "wanted") {
      const nid = this.normalizeId(id);
      if (this.collectionCache.has(nid)) {
        if (folder && folder !== "wanted") {
          await this.moveToFolder(nid, folder);
        }
        return false;
      }
      try {
        this.collectionCache.add(nid);
        this.notifyUI(nid, true);
        const now = Date.now();
        const finalMetadata = {
          id: nid,
          type: "fc2",
          ...metadata,
          updated_at: ( new Date()).toISOString(),
          lastAccessed: now,
          addedAt: metadata?.addedAt ?? now,
          folder
        };
        await Repository.details.set(nid, finalMetadata);
        this.undoManager.push({
          type: "add",
          label: `Add ${nid}`,
          snapshots: [{ id: nid, detail: finalMetadata }]
        });
        HistoryService.add(nid, this.folderToStatus(folder));
        this.emitUpdate(nid, "add");
        log$j.debug(`Added ${nid} to folder '${folder}'`);
        return true;
      } catch (e) {
        log$j.error(`Failed to add ${nid}`, e);
        this.collectionCache.delete(nid);
        this.notifyUI(nid, false);
        return false;
      }
    }
    async remove(id) {
      const nid = this.normalizeId(id);
      if (!this.collectionCache.has(nid)) return false;
      const snapshot = await Repository.details.get(nid);
      this.collectionCache.delete(nid);
      this.notifyUI(nid, false);
      await Repository.details.remove(nid);
      HistoryService.remove(nid);
      if (snapshot) {
        this.undoManager.push({
          type: "remove",
          label: `Remove ${nid}`,
          snapshots: [{ id: nid, detail: snapshot }]
        });
      }
      this.emitUpdate(nid, "remove");
      return true;
    }
    async toggle(id, metadata) {
      const nid = this.normalizeId(id);
      const wasCollected = this.has(nid);
      if (wasCollected) {
        await this.remove(nid);
      } else {
        await this.add(nid, metadata);
      }
      return !wasCollected;
    }
has(id) {
      return this.collectionCache.has(this.normalizeId(id));
    }
    getCount() {
      return this.collectionCache.size;
    }
    getIds() {
      return Array.from(this.collectionCache);
    }
    async getCollectionItems(filter, sort) {
      return this.queryService.getCollectionItems(filter, sort);
    }
    async getCollectionItemsByFolder(folder) {
      return this.queryService.getCollectionItemsByFolder(folder);
    }
    async getStats() {
      return this.queryService.getStats();
    }
    async getAllTags() {
      return this.queryService.getAllTags();
    }
    async getFolders() {
      return this.queryService.getFolders();
    }
    async getFolderCounts() {
      return this.queryService.getFolderCounts();
    }
    async findDuplicates() {
      return this.queryService.findDuplicates();
    }
async runHealthCheck(force = false) {
      return this.healthService.runHealthCheck(force);
    }
canUndo() {
      return this.undoManager.canUndo();
    }
    getUndoLabel() {
      return this.undoManager.getLabel();
    }
    async undo() {
      const action = this.undoManager.pop();
      if (!action) return false;
      log$j.debug(`Undoing: ${action.label}`);
      try {
        switch (action.type) {
          case "add":
            for (const { id } of action.snapshots) {
              this.collectionCache.delete(id);
              this.notifyUI(id, false);
              await Repository.details.remove(id);
            }
            break;
          case "remove":
          case "batch-remove":
            for (const { id, detail } of action.snapshots) {
              this.collectionCache.add(id);
              this.notifyUI(id, true);
              await Repository.details.set(id, detail);
              HistoryService.add(id, this.folderToStatus(detail.folder || "wanted"));
            }
            break;
          case "move":
          case "batch-move":
            for (const { id, detail } of action.snapshots) {
              const prevFolder = detail.folder || "wanted";
              await Repository.details.updateFolder(id, prevFolder);
              await Repository.history.markDirty(id);
            }
            break;
          case "metadata":
            for (const { id, detail } of action.snapshots) {
              await Repository.details.set(id, {
                rating: detail.rating,
                notes: detail.notes,
                userTags: detail.userTags
              });
              await Repository.history.markDirty(id);
            }
            break;
          case "merge":
            for (const { id, detail } of action.snapshots) {
              this.collectionCache.add(id);
              this.notifyUI(id, true);
              await Repository.details.set(id, detail);
              HistoryService.add(id, this.folderToStatus(detail.folder || "wanted"));
            }
            break;
        }
        this.emitUpdate(void 0, "undo");
        log$j.info(`Undo completed: ${action.label}`);
        return true;
      } catch (e) {
        log$j.error(`Undo failed: ${action.label}`, e);
        return false;
      }
    }
async updateMetadata(id, metadata) {
      const nid = this.normalizeId(id);
      const item = await Repository.details.get(nid);
      if (!item) return false;
      const snapshot = { ...item };
      if (metadata.userTags) {
        metadata.userTags = metadata.userTags.map((tag) => tag.trim()).filter((tag) => tag.length > 0).filter((tag, i, arr) => arr.indexOf(tag) === i);
      }
      if (metadata.rating !== void 0) {
        metadata.rating = Math.max(0, Math.min(5, Math.round(metadata.rating)));
      }
      await Repository.details.set(nid, { ...metadata });
      await Repository.history.markDirty(nid);
      this.undoManager.push({
        type: "metadata",
        label: `Edit metadata: ${nid}`,
        snapshots: [{ id: nid, detail: snapshot }]
      });
      this.emitUpdate(nid, "metadata");
      return true;
    }
    async getItem(id) {
      return Repository.details.get(this.normalizeId(id));
    }
    async moveToFolder(id, folder) {
      const nid = this.normalizeId(id);
      try {
        const item = await Repository.details.get(nid);
        const prevFolder = item?.folder || "wanted";
        await Repository.details.updateFolder(nid, folder);
        await Repository.history.markDirty(nid);
        if (item) {
          this.undoManager.push({
            type: "move",
            label: `Move ${nid}: ${prevFolder} → ${folder}`,
            snapshots: [{ id: nid, detail: item }]
          });
        }
        this.emitUpdate(nid, "move");
        log$j.debug(`Moved ${nid} to ${folder}`);
        return true;
      } catch (e) {
        log$j.error(`Failed to move ${nid} to ${folder}`, e);
        return false;
      }
    }
    async batchMoveToFolder(ids, folder) {
      const snapshots = [];
      const errors = [];
      for (const id of ids) {
        const detail = await Repository.details.get(id);
        if (detail) snapshots.push({ id, detail: { ...detail } });
      }
      const nids = ids.map((id) => this.normalizeId(id));
      try {
        await Repository.details.batchUpdate(nids, { folder });
        for (const nid of nids) {
          await Repository.history.markDirty(nid);
        }
        this.undoManager.push({
          type: "batch-move",
          label: `Batch move ${nids.length} items → ${folder}`,
          snapshots,
          extra: { targetFolder: folder }
        });
        this.emitUpdate(void 0, "batch-move");
        log$j.info(`Batch moved ${nids.length} items to ${folder}`);
      } catch (e) {
        const msg = e instanceof Error ? e.message : "Unknown error";
        errors.push(msg);
        log$j.error("Batch move failed", e);
      }
      return { success: errors.length === 0, affected: nids.length, errors };
    }
    async renameFolder(oldName, newName) {
      if (!oldName || !newName || oldName === newName) {
        return { success: false, affected: 0, errors: ["Invalid folder names"] };
      }
      if (SYSTEM_FOLDERS.includes(oldName)) {
        return { success: false, affected: 0, errors: ["Cannot rename system folder"] };
      }
      try {
        const items = await Repository.details.getAll();
        const targetIds = items.filter((i) => i.folder === oldName).map((i) => i.id);
        if (targetIds.length > 0) {
          await this.batchMoveToFolder(targetIds, newName);
          log$j.info(`Renamed folder '${oldName}' to '${newName}' (${targetIds.length} items)`);
        }
        return { success: true, affected: targetIds.length, errors: [] };
      } catch (e) {
        const msg = e instanceof Error ? e.message : "Unknown error";
        log$j.error("Failed to rename folder", e);
        return { success: false, affected: 0, errors: [msg] };
      }
    }
    async deleteFolder(folderName) {
      if (SYSTEM_FOLDERS.includes(folderName)) {
        return { success: false, affected: 0, errors: ["Cannot delete system folder"] };
      }
      return this.renameFolder(folderName, "wanted");
    }
    async batchRemove(ids) {
      const snapshots = [];
      const errors = [];
      for (const id of ids) {
        const detail = await Repository.details.get(id);
        if (detail) snapshots.push({ id, detail: { ...detail } });
      }
      const nids = ids.map((id) => this.normalizeId(id));
      try {
        for (const nid of nids) {
          this.collectionCache.delete(nid);
          this.notifyUI(nid, false);
        }
        await Repository.details.batchDelete(nids);
        this.undoManager.push({
          type: "batch-remove",
          label: `Batch remove ${nids.length} items`,
          snapshots
        });
        this.emitUpdate(void 0, "batch-remove");
        log$j.info(`Batch removed ${nids.length} items`);
      } catch (e) {
        const msg = e instanceof Error ? e.message : "Unknown error";
        errors.push(msg);
        log$j.error("Batch remove failed", e);
      }
      return { success: errors.length === 0, affected: nids.length, errors };
    }
    async clearAll() {
      try {
        const ids = this.getIds();
        const count = ids.length;
        await Repository.details.clear();
        this.collectionCache.clear();
        this.emitUpdate(void 0, "clear");
        log$j.info(`Collection cleared (${count} items)`);
        return { success: true, affected: count, errors: [] };
      } catch (e) {
        const msg = e instanceof Error ? e.message : "Unknown error";
        log$j.error("Failed to clear collection", e);
        return { success: false, affected: 0, errors: [msg] };
      }
    }
    async mergeItems(masterId, dupeIds) {
      const master = await Repository.details.get(masterId);
      if (!master) {
        return { success: false, affected: 0, errors: ["Master item not found"] };
      }
      const mergeSnapshots = [];
      let mergedCount = 0;
      for (const id of dupeIds) {
        if (id === masterId) continue;
        const dupe = await Repository.details.get(id);
        if (!dupe) continue;
        mergeSnapshots.push({ id, detail: { ...dupe } });
        if (!master.title && dupe.title) master.title = dupe.title;
        if (!master.imageUrl && dupe.imageUrl) master.imageUrl = dupe.imageUrl;
        if (!master.primaryImageUrl && dupe.primaryImageUrl) master.primaryImageUrl = dupe.primaryImageUrl;
        if (dupe.rating && (!master.rating || dupe.rating > master.rating)) master.rating = dupe.rating;
        if (dupe.notes) {
          master.notes = (master.notes ? master.notes + "\n\n" : "") + `[Merged from ${id}]: ${dupe.notes}`;
        }
        if (dupe.userTags && dupe.userTags.length > 0) {
          const existingTags = new Set(master.userTags || []);
          dupe.userTags.forEach((tag) => existingTags.add(tag));
          master.userTags = Array.from(existingTags);
        }
        if (dupe.addedAt && (!master.addedAt || dupe.addedAt < master.addedAt)) {
          master.addedAt = dupe.addedAt;
        }
        await this.remove(id);
        this.undoManager.pop();
        mergedCount++;
      }
      master.updated_at = ( new Date()).toISOString();
      await Repository.details.set(masterId, master);
      await Repository.history.markDirty(masterId);
      if (mergeSnapshots.length > 0) {
        this.undoManager.push({
          type: "merge",
          label: `Merge ${mergedCount} items into ${masterId}`,
          snapshots: mergeSnapshots
        });
      }
      this.emitUpdate(masterId, "merge");
      return { success: true, affected: mergedCount, errors: [] };
    }
    async exportCollection() {
      const items = await Repository.details.getAll();
      const folders = await this.getFolders();
      const folderCounts = await this.getFolderCounts();
      return {
        format: "fc2ppvdb-collection",
        version: COLLECTION_EXPORT_VERSION,
        exportedAt: ( new Date()).toISOString(),
        stats: {
          totalItems: items.length,
          folderCounts
        },
        items,
        folders
      };
    }
    async importCollection(data) {
      if (data.format !== "fc2ppvdb-collection") {
        return { success: false, affected: 0, errors: ["Invalid collection format"] };
      }
      const errors = [];
      let imported = 0;
      for (const item of data.items) {
        try {
          const nid = this.normalizeId(item.id);
          const existing = await Repository.details.get(nid);
          if (existing) {
            if (item.updated_at > existing.updated_at) {
              await Repository.details.set(nid, { ...item, id: nid });
              imported++;
            }
          } else {
            await this.add(nid, { ...item, id: nid }, item.folder || "wanted");
            this.undoManager.pop();
            imported++;
          }
        } catch (e) {
          const msg = e instanceof Error ? e.message : "Unknown error";
          errors.push(`${item.id}: ${msg}`);
        }
      }
      this.emitUpdate(void 0, "import");
      log$j.info(`Imported ${imported} items (${errors.length} errors)`);
      return { success: errors.length === 0, affected: imported, errors };
    }
  }
  const CollectionService = AppContainer.register("collection-service", new CollectionServiceImplementation());
  const StatusToggle = (id, type, metadata) => {
    const C = Config.CLASSES;
    let isActive = false;
    if (type === "wanted") {
      isActive = CollectionService.has(id);
    } else {
      isActive = HistoryService.getStatus(id) === type;
    }
    let iconOn, iconOff, tooltipOn, tooltipOff;
    let className;
    let activeClass;
    let iconClass;
    switch (type) {
      case "watched":
        iconOn = IconEye;
        iconOff = IconEyeSlash;
        tooltipOn = t("tooltipMarkAsUnviewed");
        tooltipOff = t("tooltipMarkAsViewed");
        className = "btn-toggle-view";
        activeClass = "is-viewed";
        iconClass = "icon-viewed";
        break;
      case "wanted":
        iconOn = IconStar;
        iconOff = IconStarRegular;
        tooltipOn = t("tooltipMarkAsUnwanted");
        tooltipOff = t("tooltipMarkAsWanted");
        className = "btn-toggle-wanted";
        activeClass = "is-wanted";
        iconClass = "icon-wanted";
        break;
      case "blocked":
        iconOn = IconBan;
        iconOff = IconBan;
        tooltipOn = t("tooltipMarkAsUnblocked");
        tooltipOff = t("tooltipMarkAsBlocked");
        className = "btn-toggle-blocked";
        activeClass = "is-blocked";
        iconClass = "icon-blocked";
        break;
    }
    const btn = Button$1(
      iconOn,
      isActive ? tooltipOn : tooltipOff,
      "javascript:void(0);",
      (_e) => {
        if (type === "wanted") {
          CollectionService.toggle(id, metadata);
        } else {
          const currentlyActive = btn.classList.contains(activeClass);
          if (currentlyActive) {
            HistoryService.remove(id);
          } else {
            HistoryService.add(id, type);
          }
        }
      },
      `${className} ${isActive ? activeClass : ""}`
    );
    if (iconOn !== iconOff) {
      const firstIcon = btn.querySelector(".fc2-icon");
      if (firstIcon && iconClass) firstIcon.classList.add(iconClass);
      const iconOffClass = `icon-un${type === "watched" ? "viewed" : type}`;
      const iconOffEl = UIUtils.icon(iconOff, iconOffClass);
      firstIcon?.after(iconOffEl);
    }
    const component = {
      id,
      element: btn,
      update: (raw) => {
        const data = raw;
        if (!data) return;
        const isTargetProp = type === "wanted" ? data.key === "isWanted" : data.key === "status";
        if (isTargetProp) {
          const newValue = data.value;
          const newActive = type === "wanted" ? !!newValue : newValue === type;
          btn.classList.toggle(activeClass, newActive);
          if (newActive) {
            btn.classList.add("fc2-animate-pop");
            if (type === "wanted") {
              btn.classList.add("fc2-star-burst");
              setTimeout(() => btn.classList.remove("fc2-star-burst"), TIMING.UI_TRANSITION_SLOW);
            }
            setTimeout(() => btn.classList.remove("fc2-animate-pop"), TIMING.UI_TRANSITION_NORMAL);
          }
          const currentTip = newActive ? tooltipOn : tooltipOff;
          const tt = btn.querySelector(`.${C.tooltip}`);
          if (tt) tt.textContent = currentTip;
          const btnTxt = btn.querySelector(`.${C.buttonText}`);
          if (btnTxt) btnTxt.textContent = currentTip;
        }
      }
    };
    ComponentRegistry.register(component);
    return component;
  };
  const ActressButton = (cont, actress) => {
    if (!cont || !actress || cont.querySelector(".btn-actress")) return;
    const actBtn = h(
      "button",
      {
        className: `${Config.CLASSES.resourceBtn} btn-actress`,
        onclick: (e) => {
          e.preventDefault();
          e.stopPropagation();
          UIUtils.copyButtonBehavior(actBtn, actress, t("tooltipCopied"));
        }
      },
      actress
    );
    const leftActions = cont.querySelector(".card-left-actions");
    const links = cont.querySelector(`.${Config.CLASSES.resourceLinksContainer}`);
    if (leftActions) cont.insertBefore(actBtn, leftActions);
    else if (links) cont.insertBefore(actBtn, links);
    else cont.appendChild(actBtn);
  };
  const _ActionSheet = class _ActionSheet {
    static show(title, options) {
      if (!this.backdrop) {
        this.backdrop = h("div", {
          className: "fc2-action-sheet-backdrop",
          role: "none",
          "aria-hidden": "true"
        });
        this.sheet = h("div", {
          className: "fc2-action-sheet",
          role: "dialog",
          "aria-modal": "true",
          "aria-labelledby": "fc2-sheet-title"
        });
        UIHost.add(this.backdrop);
        UIHost.add(this.sheet);
        const isMobile = "ontouchstart" in window || window.innerWidth <= 768;
        if (!isMobile) {
          this.sheet.classList.add("desktop");
        }
        this.backdrop.onclick = (e) => {
          e.preventDefault();
          e.stopPropagation();
          this.hide();
        };
        this.initTouchEvents();
      }
      this.sheet.innerHTML = "";
      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)
      );
      const closeBtn = h(
        "button",
        {
          className: "fc2-action-sheet-close-btn",
          "aria-label": t("btnClose"),
          onclick: (e) => {
            e.preventDefault();
            this.hide();
          }
        },
        "×"
      );
      header.appendChild(closeBtn);
      this.sheet.appendChild(header);
      const grid = h("div", { className: "fc2-action-sheet-grid" });
      options.forEach((opt) => {
        const iconEl = typeof opt.icon === "string" ? UIUtils.icon(opt.icon) : opt.icon;
        const item = h(
          "a",
          {
            className: "fc2-action-sheet-item",
            href: opt.url,
            target: "_blank",
            rel: "noopener noreferrer",
            role: "button"
          },
          iconEl,
          h("span", {}, opt.name)
        );
        item.onclick = () => this.hide();
        grid.appendChild(item);
      });
      this.sheet.appendChild(grid);
      requestAnimationFrame(() => {
        this.backdrop.classList.add("active");
        this.sheet.classList.add("active");
      });
      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 = "";
        const deltaY = this.touchCurrentY - this.touchStartY;
        if (deltaY > 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;
      OverlayStack.remove(this);
      this.backdrop.classList.remove("active");
      this.sheet.classList.remove("active");
      const b = this.backdrop;
      const s = this.sheet;
      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;
      }, TIMING.UI_TRANSITION_NORMAL);
    }
  };
  _ActionSheet.backdrop = null;
  _ActionSheet.sheet = null;
  _ActionSheet.touchStartY = 0;
  _ActionSheet.touchCurrentY = 0;
  let ActionSheet = _ActionSheet;
  class PortalServiceImplementation {
    constructor() {
      this.cache = new Map();
      this.PORTALS = [
        {
          id: "dmm",
          name: "DMM",
          icon: IconPlayCircle,
          urlTemplate: EXTERNAL_URLS.DMM,
          shouldShow: ({ hostname }) => !hostname.includes("dmm.co.jp")
        },
        {
          id: "fc2",
          name: "FC2",
          icon: IconLink,
          urlTemplate: EXTERNAL_URLS.FC2,
          shouldShow: ({ type, hostname }) => type === "fc2" && !hostname.includes("fc2.com")
        },
        {
          id: "supjav",
          name: "Supjav",
          icon: IconBolt,
          urlTemplate: EXTERNAL_URLS.SUPJAV,
          shouldShow: ({ hostname }) => !hostname.includes("supjav")
        },
        {
          id: "missav",
          name: "MissAV",
          icon: IconPlayCircle,
          urlTemplate: "",
shouldShow: ({ hostname }) => !hostname.includes("missav")
        },
        {
          id: "javdb",
          name: "JavDB",
          icon: IconDatabase,
          urlTemplate: EXTERNAL_URLS.JAVDB,
          shouldShow: ({ hostname }) => !hostname.includes("javdb")
        },
        {
          id: "javbus",
          name: "JavBus",
          icon: IconDatabase,
          urlTemplate: EXTERNAL_URLS.JAVBUS,
          shouldShow: ({ hostname }) => !hostname.includes("javbus")
        },
        {
          id: "javlibrary",
          name: "JavLibrary",
          icon: IconDatabase,
          urlTemplate: EXTERNAL_URLS.JAVLIBRARY,
          shouldShow: ({ hostname }) => !hostname.includes("javlibrary")
        },
        {
          id: "fc2ppvdb",
          name: "FC2PPVDB",
          icon: IconListUl,
          urlTemplate: EXTERNAL_URLS.FC2PPVDB,
          shouldShow: ({ type, hostname }) => type === "fc2" && !hostname.includes("fc2ppvdb")
        },
        {
          id: "fd2ppv",
          name: "FD2PPV",
          icon: IconServer,
          urlTemplate: EXTERNAL_URLS.FD2PPV,
          shouldShow: ({ type, hostname }) => type === "fc2" && !hostname.includes("fd2ppv")
        },
        {
          id: "sukebei",
          name: "Sukebei",
          icon: IconMagnifyingGlass,
          urlTemplate: EXTERNAL_URLS.SUKEBEI,
          shouldShow: () => true
        }
      ];
    }
    async onInit() {
      State.on("enabledPortals", () => this.clearCache());
    }
getAvailablePortals(data) {
      const hostname = location.hostname;
      const { id, type } = data;
      const cacheKey = `${id}-${type}-${hostname}`;
      const cached = this.cache.get(cacheKey);
      if (cached) return cached;
      const enabledPortals = State.proxy.enabledPortals || [];
      const results = this.PORTALS.filter(
        (portal) => enabledPortals.includes(portal.id) && portal.shouldShow({ id, type, hostname })
      ).map((portal) => {
        let url = portal.urlTemplate;
        if (portal.id === "missav") {
          url = type === "fc2" ? EXTERNAL_URLS.MISSAV_FC2 : EXTERNAL_URLS.MISSAV;
        }
        return {
          id: portal.id,
          name: portal.name,
          icon: portal.icon,
          url: (url || "").replace("{id}", () => id)
        };
      });
      this.cache.set(cacheKey, results);
      return results;
    }
getAllPortals() {
      return this.PORTALS.map((p) => ({ id: p.id, name: p.name }));
    }
    getAllSites() {
      return Array.from(new Set(this.PORTALS.map((p) => p.id)));
    }
    clearCache() {
      this.cache.clear();
    }
  }
  const PortalService = AppContainer.register("portal-service", new PortalServiceImplementation());
  const UIToolbar = {
    createDetailToolbar: (data, _markViewed, addPreviewButton) => {
      const { id, type, actress } = data;
      const C = Config.CLASSES;
      const toolbar = h("div", { className: "enh-toolbar" });
      const infoArea = h("div", { className: C.infoArea });
      const ctrls = h("div", { className: "card-top-right-controls" });
      if (State.proxy.enableHistory) {
        const toggle = StatusToggle(id, "watched");
        if (toggle && toggle.element) ctrls.appendChild(toggle.element);
      }
      const badge = Button$1(
        "",
        id,
        "javascript: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 });
      if (State.proxy.enableHistory) {
        const wanted = StatusToggle(id, "wanted");
        if (wanted && wanted.element) links.appendChild(wanted.element);
      }
      if (State.proxy.enableExternalLinks) {
        const trigger = Button$1(
          IconLink,
          t("labelExternalLinks"),
          "javascript:;",
          (e) => {
            e.preventDefault();
            e.stopPropagation();
            const portals = PortalService.getAvailablePortals({ id, type });
            if (portals.length > 0) {
              ActionSheet.show(`${id} - ${t("labelExternalLinks")}`, portals);
            }
          }
        );
        links.appendChild(trigger);
      }
      infoArea.appendChild(ctrls);
      if (actress) ActressButton(infoArea, actress);
      infoArea.appendChild(links);
      toolbar.appendChild(infoArea);
      ScraperService.fetchMagnets([{ id, type }], async (_, url) => {
        await Repository.cache.set(id, url);
        if (url) MagnetButton(links, url);
      });
      if (type === "fc2") addPreviewButton(links, id);
      return toolbar;
    }
  };
  const EnhancedCard = (data, markViewed, options = {}) => {
    const C = Config.CLASSES;
    const { id, type, title, primaryImageUrl, articleUrl, preservedIconsHTML, customClass } = data;
    const ctrls = h("div", {
      className: "card-top-right-controls",
      onclick: (e) => e.stopPropagation()
    });
    const statusToggle = State.proxy.enableHistory ? StatusToggle(id, "watched") : null;
    const wantedToggle = State.proxy.enableHistory ? StatusToggle(id, "wanted", data) : null;
    if (statusToggle) ctrls.append(statusToggle.element);
    const badge = Button$1(
      "",
      id,
      "javascript:void(0);",
      (e) => {
        e.preventDefault();
        e.stopPropagation();
        UIUtils.copyButtonBehavior(badge, id, t("tooltipCopied"));
        badge.classList.add("pulse-once");
        setTimeout(() => badge.classList.remove("pulse-once"), TIMING.UI_ANIMATION_BADGE);
      },
      C.fc2IdBadge
    );
    const btnText = badge.querySelector(`.${C.buttonText}`);
    if (btnText) btnText.textContent = id;
    ctrls.appendChild(badge);
    if (data.lastCheck) {
      const isRecent = Date.now() - data.lastCheck < 24 * 60 * 60 * 1e3;
      const healthIcon = h(
        "div",
        {
          className: "fc2-health-indicator",
          style: {
            marginLeft: "6px",
            fontSize: "10px",
            color: isRecent ? "var(--fc2-success)" : "var(--fc2-text-dim)",
            opacity: isRecent ? "1" : "0.5",
            display: "flex",
            alignItems: "center"
          },
          title: isRecent ? "Recently verified healthy" : "Last checked: " + new Date(data.lastCheck).toLocaleDateString()
        },
        isRecent ? "●" : "○"
      );
      ctrls.appendChild(healthIcon);
    }
    const initialSrc = primaryImageUrl || data.imageUrl;
    const img = h("img", {
      src: initialSrc || UI_CONSTANTS.PLACEHOLDER_IMAGE,
      className: `${C.staticPreview} ${C.previewElement}`,
      decoding: "async",
      referrerPolicy: "no-referrer",
      onload: function() {
        this.parentElement?.classList.remove("fc2-skeleton");
        this.parentElement?.classList.add("is-loaded");
        this.classList.add("fc2-reveal-content");
      },
      onerror: function() {
        const src = this.src;
        if (src.includes("wumaobi.com") && src.endsWith("/cover.jpg")) {
          this.src = src.replace("/cover.jpg", "/main.jpg");
          return;
        }
        if (src.includes("wumaobi.com") && src.endsWith("/main.jpg")) {
          this.src = EXTERNAL_URLS.FOURHOI_COVER.replace("{id}", id.padStart(7, "0"));
          return;
        }
        if (src.includes("fourhoi.com")) {
          if (data.fallbackImageUrl && src !== data.fallbackImageUrl) {
            this.src = data.fallbackImageUrl;
            return;
          }
        }
        if (src !== UI_CONSTANTS.PLACEHOLDER_IMAGE) {
          this.src = UI_CONSTANTS.PLACEHOLDER_IMAGE;
        }
      }
    });
    const previewLink = h(
      "a",
      {
        className: `${C.videoPreviewContainer} fc2-skeleton`,
        href: articleUrl || "javascript:void(0);",
        target: "_blank"
      },
      img
    );
    const links = h("div", { className: C.resourceLinksContainer });
    if (wantedToggle) links.appendChild(wantedToggle.element);
    if (State.proxy.enableExternalLinks) {
      const trigger = Button$1(
        IconLink,
        t("labelExternalLinks"),
        "javascript:;",
        (e) => {
          e.preventDefault();
          e.stopPropagation();
          const portals = PortalService.getAvailablePortals({ id, type });
          if (portals.length > 0) {
            ActionSheet.show(t("labelExternalLinks"), portals);
          } else {
            Toast.show(t("alertNoExternalLinks"), "info");
          }
        }
      );
      links.appendChild(trigger);
    }
    const info = h(
      "div",
      { className: C.infoArea },
      title ? h(
        "a",
        {
          className: C.customTitle,
          href: articleUrl || "javascript:;",
          target: "_blank",
          onclick: () => markViewed(id, card2)
        },
        title
      ) : null,
      h("div", { className: "card-left-actions" }, links)
    );
    const card2 = h(
      "div",
      {
        className: `${C.processedCard} ${type}-card ${customClass || ""} ${options.minimal ? "is-minimal" : ""}`,
        dataset: {
          id,
          type,
          previewSlug: data.previewSlug || "",
          enhSearching: "true"
        },
        onclick: (e) => {
          if (!e.target.closest(`.${C.resourceBtn}`)) {
            markViewed(id, card2);
          }
        }
      },
      previewLink,
      ctrls,
      info
    );
    if (preservedIconsHTML?.includes(PATTERNS.CENSORED_INDICATOR)) card2.classList.add(C.isCensored);
    if (HistoryService.has(id)) card2.classList.add(C.isViewed);
    const initialState = ViewStore.get(id);
    if (!options.skipFilters) {
      UIUtils.applyCardVisibility(card2, initialState.hasMagnet);
      UIUtils.applyCensoredFilter(card2);
      UIUtils.applyHistoryVisibility(card2);
    }
    const component = {
      id,
      element: card2,
update: (change) => {
        if (!change) return;
        const state = ViewStore.get(id);
        if (change.key === "isSearching") {
          if (change.value) {
            card2.dataset.enhSearching = "true";
          } else {
            delete card2.dataset.enhSearching;
            if (!options.skipFilters) {
              UIUtils.applyCardVisibility(card2, state.hasMagnet);
            }
          }
        }
        if (change.key === "hasMagnet" || change.key === "magnetUrl") {
          if (state.hasMagnet && state.magnetUrl) {
            if (!links.querySelector(`.${C.btnMagnet}`)) {
              UIBuilder.addMagnetButton(links, state.magnetUrl);
              card2.classList.remove("no-magnet");
            }
          } else if (change.key === "hasMagnet" && !change.value) {
            card2.classList.add("no-magnet");
          }
        }
        if (change.key === "status") {
          const isViewed = change.value === "watched";
          card2.classList.toggle(C.isViewed, isViewed);
          if (!options.skipFilters) {
            UIUtils.applyHistoryVisibility(card2);
          }
          statusToggle?.update?.(change);
        }
      },
      destroy: () => {
        statusToggle?.destroy?.();
        ComponentRegistry.unregister(component);
      }
    };
    ComponentRegistry.register(component);
    return { finalElement: card2, linksContainer: links, newCard: card2, component };
  };
  const PreviewButton = async (cont, id, openGallery) => {
    if (!cont || !id || cont.querySelector(".btn-gallery")) return;
    const exists = await ScraperService.checkPreviewExists(id);
    if (!exists) return;
    const previewBtn = Button$1(
      IconImages,
      t("extraPreviewTitle"),
      "javascript:;",
      async (e) => {
        const btn = e.currentTarget;
        if (btn.classList.contains(Config.CLASSES.btnLoading)) return;
        btn.classList.add(Config.CLASSES.btnLoading);
        try {
          const res = await ScraperService.fetchExtraPreviews(id);
          if (res?.length) {
            openGallery(id, res);
          } else {
            Toast.show(t("alertNoPreview"), "info");
          }
        } finally {
          btn.classList.remove(Config.CLASSES.btnLoading);
        }
      }
    );
    previewBtn.classList.add("btn-gallery");
    const magnetBtn = cont.querySelector(`.${Config.CLASSES.btnMagnet}`);
    if (magnetBtn) cont.insertBefore(previewBtn, magnetBtn);
    else cont.appendChild(previewBtn);
  };
  const UIBuilder = {
    createElement: UIUtils.h,
    markViewed: (id, el) => UIUtils.markByStatus(id, "watched", el, UIUtils.applyHistoryVisibility),
    markByStatus: (id, status, el) => UIUtils.markByStatus(id, status, el, UIUtils.applyHistoryVisibility),
    btn: Button$1,
    createEnhancedCard: (data, options = {}) => EnhancedCard(data, UIBuilder.markViewed, options),
    createExtraPreviewsGrid: UIGallery.createExtraPreviewsGrid,
    openGallery: UIGallery.openGallery,
    addPreviewButton: (cont, id) => PreviewButton(cont, id, UIGallery.openGallery),
    addActressButton: ActressButton,
    toggleLoading: (cont, show) => UIUtils.toggleLoading(cont, show, Button$1),
    addMagnetButton: (cont, url) => {
      MagnetButton(cont, url);
    },
    applyCardVisibility: UIUtils.applyCardVisibility,
    applyCensoredFilter: UIUtils.applyCensoredFilter,
    applyHistoryVisibility: UIUtils.applyHistoryVisibility,
    createDetailToolbar: (id, type, title, actress, previewSlug) => UIToolbar.createDetailToolbar(
      { id, type, title, actress, previewSlug },
      UIBuilder.markViewed,
      UIBuilder.addPreviewButton
    )
  };
  const log$i = Logger.scope("Site");
  class BaseSite {
    constructor(config) {
      this.observers = [];
      this.activeContext = PageContext.Unknown;
      this._unsubs = [];
      this.config = config;
    }
    async init() {
      try {
        log$i.debug(`Initializing ${this.config.name}`);
        this._unsubs.forEach((fn) => fn());
        this._unsubs = [];
        if (this.config.onBeforeInit) await this.config.onBeforeInit();
        this._unsubs.push(
          CoreEvents.on(AppEvents.HISTORY_LOADED, () => {
            const cards = document.querySelectorAll(`.${Config.CLASSES.processedCard}`);
            if (cards.length > 0) {
              log$i.info(`History loaded, refreshing ${cards.length} cards`);
              cards.forEach((c) => {
                const id = c.dataset.id;
                if (!id) return;
                const status = HistoryService.getStatus(id);
                if (status) {
                  UIBuilder.markByStatus(id, status, c);
                }
              });
            }
          })
        );
        this.activeContext = this.detectContext();
        if (this.config.list) 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(
          State.on(({ prop }) => {
            if (["hideNoMagnet", "enableMagnets", "hideCensored", "hideViewed", "hideBlocked"].includes(
              prop
            )) {
              this.refreshVisibility();
            }
          })
        );
      } catch (error) {
        log$i.error(`Init failed for ${this.config.name}`, error);
      }
    }
    refreshVisibility() {
      const enableMagnets = State.proxy.enableMagnets;
      const cards = document.querySelectorAll(`.${Config.CLASSES.cardRebuilt}`);
      cards.forEach((c) => {
        const card2 = c;
        const hasMagnetBtn = !!card2.querySelector(`.${Config.CLASSES.btnMagnet}`);
        const effectiveHasMagnet = !enableMagnets ? true : hasMagnetBtn;
        UIBuilder.applyCardVisibility(card2, effectiveHasMagnet);
        UIBuilder.applyCensoredFilter(card2);
        UIBuilder.applyHistoryVisibility(card2);
      });
    }
    initListMode() {
      if (!this.config.list) return;
      const list = this.config.list;
      const process = (nodes) => {
        if (nodes.length > 0)
          Logger.debug("Site", `Found ${nodes.length} potential cards via ${list.cardSelector}`);
        nodes.forEach((c) => {
          try {
            if (list.containerSelector && !c.closest(list.containerSelector)) {
              return;
            }
            if (!c.classList.contains(Config.CLASSES.cardRebuilt) && !c.hasAttribute("data-enh-rebuilding")) {
              c.setAttribute("data-enh-rebuilding", "true");
              this.processCard(c).catch((err) => {
                log$i.error("processCard failed", err);
                c.removeAttribute("data-enh-rebuilding");
              });
            }
          } catch (e) {
            log$i.error("Card iteration error", e);
          }
        });
      };
      const initialNodes = Array.from(document.querySelectorAll(list.cardSelector));
      if (initialNodes.length > 0) process(initialNodes);
      const obs = new MutationObserver((muts) => {
        const added = [];
        for (const m of muts) {
          for (const n of Array.from(m.addedNodes)) {
            if (n.nodeType !== 1) continue;
            const el = n;
            if (el.matches(list.cardSelector)) added.push(el);
            else {
              const children = el.querySelectorAll(list.cardSelector);
              if (children.length > 0) added.push(...Array.from(children));
            }
          }
        }
        if (added.length) process(added);
      });
      obs.observe(document.body, { childList: true, subtree: true });
      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)).catch((err) => {
              log$i.error("Detail action failed", err);
              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(card2) {
      if (!this.config.list) return;
      const list = this.config.list;
      try {
        let data = list.extractor(card2);
        if (data instanceof Promise) data = await data;
        if (!data) {
          Logger.trace("Site", "Extractor returned null", card2);
          card2.removeAttribute("data-enh-rebuilding");
          return;
        }
        card2.setAttribute("data-enh-processed", "true");
        const extraUi = list.getExtraUi ? list.getExtraUi(card2) : {};
        const { finalElement, newCard } = UIBuilder.createEnhancedCard({
          ...data,
          ...extraUi
        });
        if (list.postProcess) list.postProcess(card2, finalElement, newCard, data);
        card2.replaceChildren(finalElement);
        card2.classList.add(Config.CLASSES.cardRebuilt);
        card2.dataset.id = data.id;
        card2.removeAttribute("data-enh-rebuilding");
        if (card2.parentElement && !card2.parentElement.hasAttribute("data-enh-grid-container")) {
          card2.parentElement.setAttribute("data-enh-grid-container", "true");
        }
        if (newCard.classList.contains(Config.CLASSES.isCensored)) card2.classList.add(Config.CLASSES.isCensored);
        if (newCard.classList.contains(Config.CLASSES.isViewed)) card2.classList.add(Config.CLASSES.isViewed);
        UIBuilder.applyCensoredFilter(card2);
        UIBuilder.applyHistoryVisibility(card2);
        CoreEvents.emit(AppEvents.CARD_READY, {
          id: data.id,
          type: data.type,
          el: card2
        });
      } catch (error) {
        log$i.error("processCard failed", error);
      }
    }
    cleanup() {
      this.observers.forEach((o) => o.disconnect());
      this.observers = [];
      if (this.config.onCleanup) this.config.onCleanup();
    }
  }
  const fc2ppvdb = {
    name: "FC2PPVDB",
    hostnames: ["fc2ppvdb.com"],
    detectContext: () => {
      const path = location.pathname;
      if (/^\/articles\/\d+/.test(path)) return PageContext.Detail;
      if (/^\/actresses\/\d+/.test(path)) return PageContext.Search;
      return PageContext.List;
    },
    list: {
      containerSelector: "#actress-articles .flex.flex-wrap:not(.flex-end):not(.flex-between), .container .flex.flex-wrap:not(.flex-end):not(.flex-between), .max-w-screen-xl .flex.flex-wrap:not(.flex-end):not(.flex-between), main .flex.flex-wrap:not(.flex-end):not(.flex-between)",
      cardSelector: ".flex.flex-wrap > div:not(.card-rebuilt)",
      extractor: (card2) => {
        const videoInfo = MediaUtils.parseVideoId(card2.innerText, card2.querySelector("a")?.href);
        if (!videoInfo?.id) return null;
        const id = videoInfo.id;
        const isActressPage = location.pathname.includes("/actresses/");
        const globalActress = isActressPage ? MediaUtils.cleanActressName(document.querySelector(".sm\\:w-11\\/12.text-white")?.textContent) : "";
        const img = card2.querySelector("img:not(.hidden)") || card2.querySelector("img");
        const imageUrl = getBestImageSource(img);
        const writer = card2.querySelector('a[href^="/writers/"]')?.textContent?.trim();
        const actress = globalActress || writer;
        return {
          id,
          type: "fc2",
          title: (card2.querySelector("a.title-font, div.mt-1 a.text-white, h2 a")?.textContent || card2.querySelector('a[href*="/articles/"][title]')?.getAttribute("title") || `FC2-PPV-${id}`).trim(),
          primaryImageUrl: State.proxy.replaceFc2Covers ? EXTERNAL_URLS.WUMAOBI_COVER.replace("{id}", id.padStart(7, "0")) : imageUrl,
          imageUrl,
          fallbackImageUrl: imageUrl,
          articleUrl: `/articles/${id}`,
          actress: actress ?? void 0,
          previewSlug: `fc2-ppv-${id}`
        };
      },
      getExtraUi: (card2) => ({
        preservedIconsHTML: Array.from(
          card2.querySelectorAll(".float .icon, .badges span, span.absolute.top-0.right-0")
        ).map((n) => {
          const clone = n.cloneNode(true);
          if (clone.classList.contains("absolute")) {
            clone.style.position = "relative";
            clone.style.display = "inline-flex";
            clone.style.alignItems = "center";
            clone.style.marginRight = "8px";
          }
          return clone.outerHTML;
        }).join("")
      })
    },
    detail: {
      mainImageSelector: "div.lg\\:w-2\\/5",
      customDetailAction: async (cont) => {
        const pid = MediaUtils.extractFC2Id(document.querySelector(".work-title")?.textContent || "");
        const id = pid || MediaUtils.extractFC2Id(location.href) || "";
        if (!id) return;
        const title = (document.querySelector("#article-info h2")?.textContent || document.title.split("-")[0] || `FC2-PPV-${id}`).trim();
        const actress = MediaUtils.cleanActressName(document.querySelector(".actress-name")?.textContent);
        if (actress) Repository.cache.set(`actress_${id}`, actress);
        const img = cont.querySelector("img:not(.hidden)") || cont.querySelector("img");
        const primaryImageUrl = State.proxy.replaceFc2Covers ? EXTERNAL_URLS.WUMAOBI_COVER.replace("{id}", id.padStart(7, "0")) : getBestImageSource(img) || void 0;
        const { finalElement, linksContainer } = UIBuilder.createEnhancedCard({
          id,
          type: "fc2",
          title,
          actress: actress ?? void 0,
          primaryImageUrl,
          fallbackImageUrl: getBestImageSource(img) || void 0,
          articleUrl: location.href
        });
        finalElement.classList.add("is-detail");
        cont.style.padding = "0";
        cont.style.lineHeight = "0";
        cont.style.background = "transparent";
        cont.style.height = "auto";
        cont.replaceChildren(finalElement);
        ScraperService.fetchMagnets([{ id, type: "fc2" }], async (_, url) => {
          await Repository.cache.set(id, url);
          if (url) UIBuilder.addMagnetButton(linksContainer, url);
        });
        UIBuilder.addPreviewButton(linksContainer, id);
      }
    }
  };
  const fd2ppv = {
    name: "FD2PPV",
    hostnames: ["fd2ppv.cc"],
    detectContext: () => {
      ScraperService.resetFD2Backoff();
      const path = window.location.pathname;
      const segments = path.split("/").filter(Boolean);
      if (segments[0] === "articles" && segments.length >= 2 && segments[1] && /^\d+$/.test(segments[1])) {
        return PageContext.Detail;
      }
      return PageContext.List;
    },
    list: {
      containerSelector: ".artist-list, .work-list, .flex.flex-wrap:not(.flex-end):not(.flex-between), .container .grid, .other-works-grid",
      cardSelector: ".artist-card:not(.card-rebuilt)",
      extractor: (card2) => {
        const link = card2.querySelector('a[href*="/articles/"]');
        const id = MediaUtils.extractFC2Id(link?.href || "");
        const img = card2.querySelector("img.other-work-image") || card2.querySelector("img");
        const actressImg = card2.querySelector(".artist-avatar");
        const globalActress = MediaUtils.cleanActressName(
          document.querySelector(".artist-detail-name")?.textContent
        );
        const actress = MediaUtils.cleanActressName(actressImg?.getAttribute("alt") || actressImg?.title) || globalActress;
        return id ? {
          id,
          type: "fc2",
          title: (card2.querySelector(".other-work-title a")?.textContent || card2.querySelector("p a")?.textContent || card2.querySelector("p")?.textContent || card2.querySelector("h3 a")?.textContent || `FC2-PPV-${id}`).trim(),
          actress: actress ?? void 0,
          primaryImageUrl: getBestImageSource(img),
          articleUrl: link?.href || `/articles/${id}`
        } : null;
      },
      postProcess: (card2, _el, newCard) => {
        const stats = card2.querySelector(".stats");
        if (stats) {
          const infoArea = newCard.querySelector(`.${Config.CLASSES.infoArea}`);
          if (infoArea) {
            infoArea.insertBefore(stats.cloneNode(true), infoArea.querySelector(".card-left-actions"));
          }
        }
      },
      getExtraUi: (card2) => {
        const preservedIconsHTML = Array.from(card2.querySelectorAll(".float i, .float .icon")).map((n) => n.outerHTML).join("");
        return { preservedIconsHTML };
      }
    },
    detail: {
      mainImageSelector: ".work-image-large",
      customDetailAction: async (cont) => {
        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;
        if (!id) return;
        const title = (document.querySelector(".work-brief")?.textContent || document.querySelector(".work-title")?.textContent || document.title.split("|")[0] || `FC2-PPV-${id}`).replace(/\d{7,8}/, "").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() === "賣家")?.nextElementSibling?.querySelector("a")?.textContent;
        const actress = MediaUtils.cleanActressName(actressRaw);
        if (actress) Repository.cache.set(`actress_${id}`, actress);
        const img = cont.querySelector("img");
        const primaryImageUrl = getBestImageSource(img) || void 0;
        const { finalElement, linksContainer } = UIBuilder.createEnhancedCard({
          id,
          type: "fc2",
          title,
          actress: actress ?? void 0,
          primaryImageUrl,
          articleUrl: location.href
        });
        finalElement.classList.add("is-detail");
        cont.style.padding = "0";
        cont.style.lineHeight = "0";
        cont.style.background = "transparent";
        cont.style.height = "auto";
        cont.replaceChildren(finalElement);
        ScraperService.fetchMagnets([{ id, type: "fc2" }], async (_, url) => {
          await Repository.cache.set(id, url);
          if (url) UIBuilder.addMagnetButton(linksContainer, url);
        });
        UIBuilder.addPreviewButton(linksContainer, id);
      }
    }
  };
  const supjav = {
    name: "Supjav",
    hostnames: ["supjav.com"],
    detectContext: () => {
      const path = window.location.pathname;
      if (path.includes("/search/") || window.location.search.includes("s=")) return PageContext.Search;
      const cleanPath = path.replace(/^\/(zh|en|ja)\//, "/");
      const segments = cleanPath.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;
    },
    list: {
      containerSelector: ".posts.clearfix:not(:has(.swiper-wrapper)), .posts:not(:has(.swiper-wrapper))",
      cardSelector: ".post:not(.card-rebuilt)",
      extractor: (card2) => {
        const tLink = card2.querySelector('h3 a, a[rel="bookmark"]');
        const img = card2.querySelector("img.thumb, img");
        const text = tLink?.title || tLink?.textContent || img?.alt || "";
        const info = MediaUtils.parseVideoId(text, tLink?.href || "");
        if (!info) return null;
        return {
          ...info,
          title: text.trim(),
          primaryImageUrl: MediaUtils.cleanImageUrl(
            card2.querySelector("img")?.getAttribute("data-original") || getBestImageSource(img) || ""
          ),
          articleUrl: tLink?.href,
          previewSlug: info.previewSlug || null
        };
      },
      postProcess: (card2, _el, newCard, data) => {
        const C = Config.CLASSES;
        if (data.title?.includes("[有]") || card2.innerText.includes("有码")) {
          card2.classList.add(C.isCensored);
          newCard.classList.add(C.isCensored);
        }
        const meta = card2.querySelector(".meta");
        if (meta) {
          const infoArea = newCard.querySelector(`.${C.infoArea}`);
          if (infoArea) infoArea.insertBefore(meta.cloneNode(true), infoArea.querySelector(".card-left-actions"));
        }
      }
    },
    detail: {
      triggerSelector: ".archive-title h1, h1.entry-title, .post-title h1",
      customDetailAction: async (titleEl) => {
        await new Promise((r) => setTimeout(r, TIMING.SCRIPT_INJECTION_DELAY));
        try {
          const video = document.querySelector("#dz_video, .video-container, .entry-content .video-player");
          const info = MediaUtils.parseVideoId(titleEl.textContent || "", location.href);
          if (!info) return;
          const actress = MediaUtils.cleanActressName(
            document.querySelector(".post-meta a")?.textContent
          );
          const toolbar = UIBuilder.createDetailToolbar(
            info.id,
            info.type,
            titleEl.textContent?.trim() || "",
            actress ?? void 0,
            info.previewSlug ?? void 0
          );
          if (video) {
            video.after(toolbar);
          } else {
            titleEl.after(toolbar);
          }
          if (info.type === "fc2") {
            const fetchedActress = await ScraperService.fetchActressFromFD2(info.id);
            if (fetchedActress) {
              const infoArea = toolbar.querySelector(`.${Config.CLASSES.infoArea}`);
              if (infoArea) UIBuilder.addActressButton(infoArea, fetchedActress);
            }
          }
        } catch (err) {
          Logger.warn("Supjav", "Detail injection failed", err);
        }
      }
    }
  };
  const missav = {
    name: "MissAV",
    hostnames: ["missav.ws", "missav.ai"],
    detectContext: () => {
      const path = window.location.pathname;
      const segments = path.split("/").filter(Boolean);
      const lastSegment = (segments[segments.length - 1] || "").toLowerCase();
      const isDetailPattern = /^(fc2-ppv-|[a-z]{2,10}-)\d+/i.test(lastSegment) || /^[a-z0-9]{15,}$/.test(lastSegment);
      if (isDetailPattern && segments.length <= 3) {
        return PageContext.Detail;
      }
      const listBlacklist = ["search", "new", "actress", "maker", "dm", "genres", "series", "tags", "makers"];
      if (segments.some((s) => listBlacklist.some((b) => s.toLowerCase().startsWith(b)))) return PageContext.List;
      if (segments.length === 1 && !listBlacklist.includes(lastSegment)) {
        return PageContext.Detail;
      }
      return path.includes("/search/") ? PageContext.Search : PageContext.List;
    },
    list: {
      containerSelector: "main, .grid, .sm\\:container, div.posts, #main",
      cardSelector: 'div.grid[class*="grid-cols-"] > div:not(.card-rebuilt), div.thumbnail:not(.card-rebuilt)',
      extractor: (card2) => {
        if (card2.closest('[x-for*="recommend"]') || card2.closest('[x-for*="trending"]') || card2.querySelector('[x-text*="item."]') || card2.hasAttribute("@mouseenter") || card2.hasAttribute("x-show")) {
          return null;
        }
        const tLink = card2.querySelector(
          [
            "a.text-secondary",
            "a.hover\\:text-primary",
            "div.my-2 a",
            "div.mt-1 a",
            ".video-title a",
            ".thumbnail + div a"
          ].join(",")
        );
        const img = card2.querySelector("img");
        const video = card2.querySelector("video");
        const anyLink = tLink || card2.querySelector(
          'a[href*="/fc2-ppv-"], a[href*="/en/"], a[href*="/ja/"], a[href*="/cn/"]'
        );
        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 previewSlug = video?.dataset.src?.match(/fourhoi\.com\/([^/]+)\/preview\.mp4/)?.[1] || video?.src?.match(/fourhoi\.com\/([^/]+)\/preview\.mp4/)?.[1] || info.previewSlug || null;
        return {
          ...info,
          title: anyLink.textContent?.trim() || "",
          imageUrl: getBestImageSource(img),
          articleUrl: anyLink.href,
          previewSlug
        };
      },
      postProcess: (card2) => {
        card2.removeAttribute("x-data");
        card2.addEventListener("mouseenter", (e) => e.stopPropagation(), true);
      }
    },
    detail: {
      triggerSelector: 'div[x-data*="player"], div.player-container, h1.text-base, div.mt-4',
      customDetailAction: async (el, obs) => {
        const title = document.querySelector("h1.text-base")?.textContent || document.title;
        const info = MediaUtils.parseVideoId(title, location.href);
        if (!info) return;
        let actress = null;
        if (info.type === "jav") {
          const contentArea = document.querySelector("div.mt-4, div.text-secondary, main");
          const actresses = Array.from((contentArea || document).querySelectorAll('a[href*="/actresses/"]')).map(
            (a) => MediaUtils.cleanActressName(a.textContent)
          );
          actress = actresses.find((a) => !!a) || null;
        }
        const toolbar = UIBuilder.createDetailToolbar(
          info.id,
          info.type,
          title,
          actress ?? void 0,
          info.previewSlug ?? void 0
        );
        const target = document.querySelector("h1.text-base") || el;
        target.insertAdjacentElement("afterend", toolbar);
        if (info.type === "fc2") {
          const fetchedActress = await ScraperService.fetchActressFromFD2(info.id);
          if (fetchedActress) {
            const infoArea = toolbar.querySelector(`.${Config.CLASSES.infoArea}`);
            if (infoArea) UIBuilder.addActressButton(infoArea, fetchedActress);
          }
        }
        obs?.disconnect();
      }
    }
  };
  const javdb = {
    name: "JavDB",
    hostnames: ["javdb.com", "javdb565.com"],
    detectContext: () => {
      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;
    },
    list: {
      containerSelector: ".movie-list, .tile-images:not(.preview-images)",
      cardSelector: ".item:not(.card-rebuilt), .tile-item:not(.card-rebuilt)",
      extractor: (card2) => {
        const link = card2.matches('a.box, a.tile-item, a[href^="/v/"]') ? card2 : card2.querySelector('a.box, a.tile-item, a[href^="/v/"]');
        if (!link) return null;
        const idStrong = card2.querySelector(".video-title strong, .video-number");
        const text = idStrong ? idStrong.textContent : link.title || link.innerText;
        const img = card2.querySelector("img");
        const info = MediaUtils.parseVideoId(text || "", link.href);
        return info ? {
          ...info,
          title: (card2.querySelector(".video-title")?.textContent || link.title || text || "").trim(),
          imageUrl: getBestImageSource(img),
          articleUrl: link.href,
          previewSlug: info.previewSlug || null
        } : null;
      },
      postProcess: (card2, _el, newCard, _data) => {
        const score = card2.querySelector(".score");
        const meta = card2.querySelector(".meta");
        const infoArea = newCard.querySelector(`.${Config.CLASSES.infoArea}`);
        if (infoArea) {
          if (score) infoArea.insertBefore(score.cloneNode(true), infoArea.querySelector(".card-left-actions"));
          if (meta) infoArea.insertBefore(meta.cloneNode(true), infoArea.querySelector(".card-left-actions"));
        }
      }
    },
    detail: {
      mainImageSelector: ".column-video-cover",
      customDetailAction: async (cont) => {
        const titleEl = document.querySelector("h2.title");
        const info = MediaUtils.parseVideoId(titleEl?.textContent || "", location.href);
        if (info) {
          const img = cont.querySelector("img.video-cover");
          const actress = MediaUtils.cleanActressName(cont.querySelector(".meta")?.textContent);
          const { finalElement, linksContainer } = UIBuilder.createEnhancedCard({
            id: info.id,
            type: info.type,
            title: titleEl?.textContent?.trim() || "",
            primaryImageUrl: img?.src || "",
            articleUrl: location.href,
            previewSlug: info.previewSlug ?? void 0
          });
          finalElement.classList.add("is-detail");
          Array.from(cont.children).forEach((child) => {
            if (child instanceof HTMLElement) child.style.display = "none";
          });
          cont.appendChild(finalElement);
          cont.style.maxWidth = "100%";
          const magnetLinks = Array.from(
            document.querySelectorAll('#magnets-content a[href^="magnet:?"]')
          );
          const firstLink = magnetLinks[0];
          if (magnetLinks.length > 0 && firstLink) {
            Repository.cache.set(info.id, firstLink.href);
            magnetLinks.slice(0, 3).forEach((m) => UIBuilder.addMagnetButton(linksContainer, m.href));
          } else {
            ScraperService.fetchMagnets([{ id: info.id, type: info.type }], async (_, url) => {
              await Repository.cache.set(info.id, url);
              if (url && linksContainer) UIBuilder.addMagnetButton(linksContainer, url);
            });
          }
          if (info.type === "fc2") {
            UIBuilder.addPreviewButton(linksContainer, info.id);
            if (!actress) {
              const fetchedActress = await ScraperService.fetchActressFromFD2(info.id);
              if (fetchedActress) {
                const infoArea = finalElement.querySelector(`.${Config.CLASSES.infoArea}`);
                if (infoArea) UIBuilder.addActressButton(infoArea, fetchedActress);
              }
            }
          }
          const bindNativeButton = (selector) => {
            const btn = document.querySelector(selector);
            if (btn && !btn.hasAttribute("data-hooked")) {
              btn.setAttribute("data-hooked", "true");
              btn.addEventListener("click", () => {
                HistoryService.add(info.id);
              });
            }
          };
          bindNativeButton('form.button_to[action*="/reviews/watched"] button');
          bindNativeButton("button.js-watched-video");
        }
      }
    }
  };
  const sukebei = {
    name: "Sukebei",
    hostnames: ["sukebei.nyaa.si"],
    detectContext: () => {
      return PageContext.List;
    },
    list: {
      containerSelector: "table.torrent-list tbody",
      cardSelector: "tr",
      extractor: () => null
    },
    detail: {
      customDetailAction: async () => {
      }
    }
  };
  const ocili = {
    name: "0cili",
    hostnames: ["0cili.org", "0cili.cc"],
    detectContext: () => {
      return PageContext.List;
    },
    list: {
      containerSelector: "table.torrent-list tbody",
      cardSelector: "tr",
      extractor: () => null
    },
    detail: {
      customDetailAction: async () => {
      }
    }
  };
  const SiteRegistry = {
    fc2ppvdb,
    fd2ppv,
    supjav,
    missav,
    javdb,
    sukebei,
    ocili
  };
  const SiteConfigs = SiteRegistry;
  const log$h = Logger.scope("SiteManager");
  class GenericSite extends BaseSite {
    detectContext() {
      if (this.config.detectContext) {
        try {
          return this.config.detectContext();
        } catch (e) {
          log$h.error("Custom detectContext failed", e);
        }
      }
      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.List;
    }
  }
  class SiteServiceImplementation {
    constructor() {
      this.registry = new Map();
      this.activeSite = null;
      this.currentUrl = location.href;
    }
    async onBootstrap() {
      log$h.debug("Bootstrapped, initiating site matching");
      this.registerAll(SiteConfigs);
      await this.bootstrap();
    }
    registerAll(configs) {
      Object.entries(configs).forEach(([name, config]) => {
        config.name = name;
        this.registry.set(name, config);
      });
    }
    async bootstrap() {
      const hostname = location.hostname;
      let matchedConfig = null;
      for (const config of this.registry.values()) {
        const matches = config.hostnames.some(
          (hn) => typeof hn === "string" ? hostname.includes(hn) : hn.test(hostname)
        );
        if (matches) {
          matchedConfig = config;
          break;
        }
      }
      if (matchedConfig) {
        log$h.info(`Matched site: ${matchedConfig.name}`);
        this.activeSite = new GenericSite(matchedConfig);
        await this.activeSite.init();
        this.initUrlWatcher();
        CoreEvents.emit(AppEvents.SITE_READY, { siteName: matchedConfig.name });
      } else {
        log$h.warn(`No site config matched for ${hostname}`);
      }
    }
    initUrlWatcher() {
      const check = () => {
        if (location.href !== this.currentUrl) {
          this.currentUrl = location.href;
          log$h.debug("URL change detected");
          this.activeSite?.init();
        }
      };
      const origPushState = history.pushState.bind(history);
      const origReplaceState = history.replaceState.bind(history);
      history.pushState = function(...args) {
        const res = origPushState(...args);
        check();
        return res;
      };
      history.replaceState = function(...args) {
        const res = origReplaceState(...args);
        check();
        return res;
      };
      window.addEventListener("popstate", check);
    }
    getActiveSite() {
      return this.activeSite;
    }
  }
  AppContainer.register("site-service", new SiteServiceImplementation());
  const log$g = Logger.scope("Magnet");
  const CACHE_NO_MAGNET = "@@NO_MAGNET@@";
  class MagnetServiceImplementation {
    constructor() {
      this.queue = new Map();
      this.activeSearches = new Set();
      this.maxConcurrency = MAGNET_CONFIG.MAX_CONCURRENCY;
      this.onProgress = null;
      this.flushTimer = null;
    }
    onBootstrap() {
      log$g.debug("Subscribed to UI_READY");
      CoreEvents.on(AppEvents.UI_READY, () => {
        log$g.info("Manager active");
      });
    }
    async fetchMagnet(id, type) {
      const cached = await Repository.cache.get(id);
      if (cached) {
        if (cached === CACHE_NO_MAGNET) return null;
        return cached;
      }
      if (this.queue.has(id)) {
        const task = this.queue.get(id);
        return new Promise((resolve) => {
          const originalResolve = task.resolve;
          task.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);
        setTimeout(() => {
          const t2 = this.queue.get(id);
          if (t2 === task && t2.status !== "found" && t2.status !== "failed") {
            this._onTimeout(t2);
            this.queue.delete(id);
          }
        }, MAGNET_CONFIG.SEARCH_TIMEOUT_MS);
        this._requestProcess();
      });
    }
    _onTimeout(task) {
      if (task.status === "found" || task.status === "failed") return;
      log$g.warn(`Search timed out for ${task.id}`);
      task.status = "failed";
      this._notifyUI(task.id, "failed");
      task.resolve(null);
    }
    _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 pending = Array.from(this.queue.values()).filter((t2) => t2.status === "pending").sort((a, b) => a.startTime - b.startTime);
      if (pending.length === 0) return;
      const batchSize = NETWORK.CHUNK_SIZE;
      const batch = pending.slice(0, batchSize);
      batch.forEach((task) => {
        task.status = "searching";
        this.activeSearches.add(task.id);
      });
      this._updateStatus();
      try {
        await this._executeSearchBatch(batch);
      } catch (error) {
        log$g.error("Batch search error", error);
      } finally {
        batch.forEach((task) => this.activeSearches.delete(task.id));
        this._requestProcess();
      }
    }
    async _executeSearchBatch(batch) {
      const ids = batch.map((t2) => t2.id);
      log$g.debug(`Searching batch of ${ids.length} items`);
      await ScraperService.fetchMagnets(
        batch.map((t2) => ({ id: t2.id, type: t2.type })),
        (id, url) => {
          const task = this.queue.get(id);
          if (!task) return;
          if (url) {
            this._onTaskSuccess(task, url);
          } else {
            this._onTaskFailed(task, true);
          }
        }
      );
    }
    async _onTaskSuccess(task, url) {
      await Repository.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$g.debug(`Retrying ${task.id} (${task.retryCount}/${maxRetries})`);
      } else {
        task.status = "failed";
        task.resolve(null);
        this.queue.delete(task.id);
        this._notifyUI(task.id, "failed");
        await Repository.cache.set(task.id, CACHE_NO_MAGNET);
      }
      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;
      const all = Array.from(this.queue.values());
      this.onProgress({
        total: this.queue.size,
        active: this.activeSearches.size,
        found: 0,
        failed: all.filter((t2) => t2.status === "failed").length
      });
    }
    predictiveSearch(card2) {
      const { id, type } = card2.dataset;
      if (id && type && !this.queue.has(id)) {
        if (this.queue.size < MAGNET_CONFIG.PREDICTIVE_LIMIT) {
          this.fetchMagnet(id, type);
        }
      }
    }
  }
  const MagnetService = AppContainer.register("magnet-service", new MagnetServiceImplementation());
  const SmartTooltips = {
    currentTooltip: null,
    hideTimeout: null,
    init() {
      document.addEventListener(
        "mouseover",
        (e) => {
          const target = e.target;
          if (!(target instanceof Element)) return;
          const tooltipText = target.getAttribute("data-tooltip") || target.title;
          if (tooltipText && this.shouldShowTooltip(target)) {
            this.show(target, tooltipText);
            target._hasTooltip = true;
          }
        },
        true
      );
      document.addEventListener(
        "mouseout",
        (e) => {
          const target = e.target;
          if (!(target instanceof Element)) return;
          if (target.hasAttribute("data-tooltip") || target.title) {
            this.hide();
          }
        },
        true
      );
    },
    shouldShowTooltip(element) {
      if ("ontouchstart" in window) return false;
      if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") return false;
      return true;
    },
    show(element, text) {
      this.hide();
      const tooltip = h(
        "div",
        {
          className: "smart-tooltip",
          style: `
                position: fixed;
                background: rgba(0, 0, 0, 0.9);
                color: ${UI_TOKENS.COLORS.WHITE};
                padding: ${UI_TOKENS.SPACING.XS} ${UI_TOKENS.SPACING.MD};
                border-radius: ${UI_TOKENS.RADIUS.MD};
                font-size: 12px;
                z-index: ${UI_CONSTANTS.Z_INDEX_TOOLTIP};
                pointer-events: none;
                white-space: nowrap;
                max-width: 300px;
                backdrop-filter: blur(${UI_TOKENS.BACKDROP.BLUR});
                box-shadow: ${UI_TOKENS.BACKDROP.SHADOW};
                opacity: 0;
                will-change: transform, opacity;
                transition: opacity 0.2s, transform 0.2s;
                transform: translateY(5px);
            `
        },
        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;
      }
      tooltip.style.top = `${top}px`;
      tooltip.style.left = `${left}px`;
      requestAnimationFrame(() => {
        tooltip.style.opacity = "1";
      });
      this.currentTooltip = tooltip;
    },
    hide() {
      if (this.currentTooltip) {
        this.currentTooltip.style.opacity = "0";
        setTimeout(() => {
          if (this.currentTooltip) {
            this.currentTooltip.remove();
            this.currentTooltip = null;
          }
        }, 200);
      }
    }
  };
  const GlobalClick = {
    init() {
      document.addEventListener("click", (e) => {
        const target = e.target;
        if (!target.closest(".enh-dropdown")) {
          document.querySelectorAll(".enh-dropdown.active").forEach((d) => {
            d.classList.remove("active");
            const card2 = d.closest(".processed-card") || d.closest(".card-rebuilt");
            if (card2) card2.classList.remove("has-active-dropdown");
          });
        }
      });
    }
  };
  const log$f = Logger.scope("UI");
  class UIEnhancementService {
    constructor() {
      this.observer = null;
    }
    onInit() {
      log$f.debug("Initializing UI interactions");
      SmartTooltips.init();
      GlobalClick.init();
      this.initObserver();
      this.bindStateListeners();
      this.bindEventListeners();
      this.updateGlobalClasses();
      this.updateVisibilityForCards();
    }
    initObserver() {
      this.observer = new IntersectionObserver(
        (entries) => {
          entries.forEach((entry) => {
            if (entry.isIntersecting) {
              this.processVisibleCard(entry.target);
              this.observer?.unobserve(entry.target);
            }
          });
        },
        { rootMargin: "200px" }
      );
    }
    processVisibleCard(cardEl) {
      const C = Config.CLASSES;
      if (State.proxy.enableHistory && cardEl.dataset.id) {
        const isViewed = UIUtils.hasHistory(cardEl.dataset.id);
        cardEl.classList.toggle(C.isViewed, isViewed);
      }
      UIUtils.applyHistoryVisibility(cardEl);
      UIUtils.applyCollectionFilter(cardEl);
      const hasMagnet = !!cardEl.querySelector(`.${C.btnMagnet}`) || cardEl.dataset.hasMagnet === "true";
      UIUtils.applyCardVisibility(cardEl, hasMagnet);
      UIUtils.applyCensoredFilter(cardEl);
    }
    bindStateListeners() {
      State.on("showViewedBtn", () => this.updateGlobalClasses());
      State.on("showIdBadge", () => this.updateGlobalClasses());
      const filterProps = [
        "hideViewed",
        "hideNoMagnet",
        "hideCensored",
        "hideBlocked",
        "hideUnwanted"
      ];
      filterProps.forEach((prop) => {
        State.on(prop, () => this.updateVisibilityForCards());
      });
    }
    bindEventListeners() {
      CoreEvents.on(AppEvents.CARD_READY, async ({ id, type, el }) => {
        const C = Config.CLASSES;
        const container2 = el.querySelector(`.${C.resourceLinksContainer}`);
        if (!container2) return;
        ViewStore.set(id, "isSearching", true);
        el.dataset.enhSearching = "true";
        if (type === "fc2" && State.proxy.loadExtraPreviews) {
          UIBuilder.addPreviewButton(container2, id);
        }
        if (State.proxy.enableMagnets) {
          try {
            const cached = await Repository.cache.get(id);
            if (cached) {
              CoreEvents.emit(AppEvents.MAGNET_FOUND, { id, url: cached });
              return;
            }
            MagnetService.fetchMagnet(id, type).catch((err) => {
              log$f.error(`Fetch magnet failed for ${id}`, err);
            });
          } catch (err) {
            log$f.error(`Magnet cache lookup failed for ${id}`, err);
            CoreEvents.emit(AppEvents.MAGNET_FAILED, { id });
          }
        } else {
          CoreEvents.emit(AppEvents.MAGNET_FAILED, { id });
        }
      });
      CoreEvents.on(AppEvents.MAGNET_FOUND, (data) => this.handleMagnetResult(data.id, data.url));
      CoreEvents.on(AppEvents.MAGNET_FAILED, (data) => this.handleMagnetResult(data.id, null));
      CoreEvents.on(AppEvents.HISTORY_ADDED, (data) => this.syncUIWithHistory(data.id, data.status));
      CoreEvents.on(AppEvents.HISTORY_REMOVED, (data) => this.syncUIWithHistory(data.id, "none"));
      CoreEvents.on(AppEvents.HISTORY_LOADED, () => this.updateVisibilityForCards());
    }
    updateGlobalClasses() {
      const isShowViewed = State.proxy.showViewedBtn !== false;
      const isShowId = State.proxy.showIdBadge !== false;
      document.body.classList.toggle("hide-viewed-btn", !isShowViewed);
      document.body.classList.toggle("hide-id-badge", !isShowId);
    }
    updateVisibilityForCards() {
      const C = Config.CLASSES;
      document.querySelectorAll(`.${C.cardRebuilt}`).forEach((card2) => {
        const cardEl = card2;
        this.observer?.observe(cardEl);
      });
    }
    handleMagnetResult(id, url) {
      ViewStore.set(id, "isSearching", false);
      ViewStore.set(id, "hasMagnet", !!url);
      ViewStore.set(id, "magnetUrl", url);
    }
    syncUIWithHistory(id, status) {
      ViewStore.set(IdNormalizer.normalize(id), "status", status);
    }
  }
  AppContainer.register("ui-enhancements", new UIEnhancementService());
  const log$e = Logger.scope("Supabase");
  class SupabaseClient {
    static getConfig() {
      let url = State.proxy.supabaseUrl || "";
      if (url.endsWith("/")) url = url.slice(0, -1);
      url = url.trim();
      const key = (State.proxy.supabaseKey || "").trim();
      if (key) {
        const masked = key.length > 10 ? `${key.slice(0, 4)}...${key.slice(-4)}` : "***";
        log$e.debug(`Using API Key (len: ${key.length}, masked: ${masked})`);
      }
      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");
      log$e.trace(`Request: ${method} ${endpoint}`, { body });
      try {
        const response = await http(`${url}${endpoint}`, {
          method,
          headers: {
            apikey: key,
            Authorization: `Bearer ${key}`,
            "Content-Type": "application/json",
            ...headers
          },
          data: body ?? void 0
        });
        log$e.trace(`Response: ${method} ${endpoint}`, { response });
        return response;
      } catch (e) {
        const err = e;
        if (err.status === 401 || err.status === 403) {
          const { key: key2 } = this.getConfig();
          if (key2.length < 100) {
            log$e.warn("Auth failed, API key may not be a standard JWT");
          }
        }
        log$e.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]) {
            const payload = JSON.parse(atob(parts[1]));
            if (payload.exp * 1e3 > Date.now() + 6e4) return `Bearer ${jwt}`;
          }
        } catch {
        }
      }
      const refresh2 = GM_getValue(STORAGE_KEYS.SUPABASE_REFRESH);
      if (!refresh2) return null;
      try {
        const data = await this.request(
          `${SUPABASE_ENDPOINTS.TOKEN}?grant_type=refresh_token`,
          "POST",
          { refresh_token: refresh2 }
        );
        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$e.error("Token refresh failed", e);
        const err = e;
        if (err.status === 400 || err.status === 401 || err.response && err.response.includes("invalid_grant")) {
          [
            STORAGE_KEYS.SYNC_USER_ID,
            STORAGE_KEYS.SUPABASE_JWT,
            STORAGE_KEYS.SUPABASE_REFRESH,
            STORAGE_KEYS.CURRENT_USER_EMAIL,
            STORAGE_KEYS.LAST_SYNC_TS
          ].forEach((k) => GM_deleteValue(k));
        }
      }
      return null;
    }
  }
  const log$d = Logger.scope("Supabase");
  class SupabaseProviderImpl {
    constructor() {
      this.name = "supabase";
      this.isSyncing = false;
      this.needsRetry = false;
      this._serverSupportsMetadata = true;
      this._onProgress = null;
    }
    onInit() {
    }
    logout(silent = false) {
      [
        STORAGE_KEYS.SYNC_USER_ID,
        STORAGE_KEYS.SUPABASE_JWT,
        STORAGE_KEYS.SUPABASE_REFRESH,
        STORAGE_KEYS.CURRENT_USER_EMAIL,
        STORAGE_KEYS.LAST_SYNC_TS
      ].forEach((k) => GM_deleteValue(k));
      if (!silent) {
        CoreEvents.emit(AppEvents.SHOW_TOAST, { message: t("alertLoggedOut"), type: "success" });
        setTimeout(() => location.reload(), TIMING.RELOAD_DELAY_FAST);
      }
    }
    async login(e, p) {
      const data = await SupabaseClient.request(
        `${SUPABASE_ENDPOINTS.TOKEN}?grant_type=password`,
        "POST",
        { email: e, password: p }
      );
      if (data?.access_token) {
        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);
        GM_setValue(STORAGE_KEYS.CURRENT_USER_EMAIL, data.user.email);
        GM_setValue(STORAGE_KEYS.LAST_SYNC_TS, UI_CONSTANTS.DEFAULT_TIMESTAMP);
        return data.user;
      }
      throw new Error("Login failed");
    }
    async signup(e, p) {
      return SupabaseClient.request(SUPABASE_ENDPOINTS.SIGNUP, "POST", { email: e, password: p });
    }
    get onProgress() {
      return this._onProgress;
    }
    set onProgress(val) {
      this._onProgress = val;
    }
    async performSync(isManual = false, forceRefresh = false, preferRemote = false, traceId) {
      if (this.isSyncing) {
        this.needsRetry = true;
        return;
      }
      if (preferRemote) forceRefresh = true;
      const runSync = async () => {
        Logger.group("Supabase", `Sync (Force: ${forceRefresh})`, traceId);
        this.isSyncing = true;
        if (isManual) CoreEvents.emit(AppEvents.SHOW_TOAST, { message: t("labelSyncing"), type: "info" });
        try {
          State.proxy.syncStatus = SYNC_STATUS.SYNCING;
          const auth = await SupabaseClient.getAuthHeader();
          if (!auth) {
            log$d.debug("No auth session, skipping sync", void 0, traceId);
            State.proxy.syncStatus = SYNC_STATUS.IDLE;
            if (isManual)
              CoreEvents.emit(AppEvents.SHOW_TOAST, {
                message: t("alertLoginRequired"),
                type: "warn"
              });
            return;
          }
          const lastSync = forceRefresh ? UI_CONSTANTS.DEFAULT_TIMESTAMP : GM_getValue(STORAGE_KEYS.LAST_SYNC_TS, UI_CONSTANTS.DEFAULT_TIMESTAMP);
          const syncStartedAt = ( new Date()).toISOString();
          if (!preferRemote) {
            const dirty = await Repository.db.history.where("sync_dirty").equals(1).limit(200).toArray();
            if (dirty.length > 0) {
              const userId = GM_getValue(STORAGE_KEYS.SYNC_USER_ID);
              if (this._onProgress) this._onProgress({ phase: "Pushing local data", percent: 30 });
              const payload = await Promise.all(
                dirty.map(async (r) => {
                  const item = {
                    fc2_id: isNaN(Number(r.id)) ? r.id : parseInt(r.id, 10),
                    last_watched_at: new Date(r.timestamp).toISOString(),
                    status: r.status || "watched",
                    is_deleted: !!r.is_deleted,
                    user_id: userId || null,
                    metadata: null
                  };
                  if (this._serverSupportsMetadata) {
                    const details = r.status === "wanted" ? await Repository.details.get(r.id) : null;
                    item.metadata = details || null;
                  } else {
                    delete item.metadata;
                  }
                  return item;
                })
              );
              if (payload.length > 0) {
                const keys = Object.keys(payload[0]).sort();
                log$d.debug("Sync payload", {
                  count: payload.length,
                  keys
                });
                if (payload.length > 1) {
                  const keysLast = Object.keys(payload[payload.length - 1]).sort().join(",");
                  if (keys.join(",") !== keysLast) {
                    log$d.error("Payload keys inconsistent", {
                      first: keys.join(","),
                      last: keysLast
                    });
                  }
                }
              }
              await SupabaseClient.request(SUPABASE_ENDPOINTS.USER_HISTORY, "POST", payload, {
                Authorization: auth,
                Prefer: "resolution=merge-duplicates"
              });
              await Repository.db.history.where("id").anyOf(dirty.map((r) => r.id)).modify({ sync_dirty: 0, retry_count: 0 });
            }
          }
          if (this._onProgress) this._onProgress({ phase: "Fetching remote data", percent: 60 });
          let page = 0, hasMore = true;
          while (hasMore) {
            const remote = await SupabaseClient.request(
              `${SUPABASE_ENDPOINTS.USER_HISTORY}?updated_at=gt.${encodeURIComponent(lastSync)}&select=fc2_id,last_watched_at,is_deleted,updated_at,status${this._serverSupportsMetadata ? ",metadata" : ""}&order=updated_at.asc`,
              "GET",
              null,
              { Authorization: auth, Range: `${page * 1e3}-${(page + 1) * 1e3 - 1}` }
            );
            if (remote?.length) {
              const history2 = [], deletes = [];
              remote.forEach((i) => {
                if (i.is_deleted) deletes.push(String(i.fc2_id));
                else
                  history2.push({
                    id: String(i.fc2_id),
                    timestamp: new Date(i.last_watched_at).getTime(),
                    status: i.status || "watched",
                    updated_at: i.updated_at,
                    is_deleted: 0,
                    sync_dirty: 0
                  });
              });
              await Repository.db.transaction(
                "rw",
                Repository.db.history,
                Repository.db.itemDetails,
                async () => {
                  if (history2.length) await Repository.db.history.bulkPut(history2);
                  if (deletes.length)
                    await Repository.db.history.where("id").anyOf(deletes).modify({ is_deleted: 1, sync_dirty: 0 });
                  for (const i of remote)
                    if (i.metadata && !i.is_deleted)
                      await Repository.details.set(String(i.fc2_id), i.metadata);
                }
              );
              if (remote.length < 1e3) hasMore = false;
              else page++;
            } else hasMore = false;
          }
          await HistoryService.load();
          GM_setValue(STORAGE_KEYS.LAST_SYNC_TS, syncStartedAt);
          State.proxy.syncStatus = SYNC_STATUS.SUCCESS;
          log$d.info("Sync completed");
          if (isManual)
            CoreEvents.emit(AppEvents.SHOW_TOAST, {
              message: t("alertWebDAVSyncSuccess"),
              type: "success"
            });
        } catch (e) {
          const err = e;
          if (err.status === 400 || err.message?.includes("metadata")) {
            log$d.warn("Metadata column missing, falling back", void 0, traceId);
            this._serverSupportsMetadata = false;
            this.needsRetry = true;
          } else {
            log$d.error("Sync failed", e, traceId);
          }
          State.proxy.syncStatus = SYNC_STATUS.ERROR;
          if (isManual)
            CoreEvents.emit(AppEvents.SHOW_TOAST, {
              message: t("alertWebDAVSyncError") + (err.message || ""),
              type: "error"
            });
        } finally {
          this.isSyncing = false;
          Logger.groupEnd();
          if (this.needsRetry) {
            this.needsRetry = false;
            this.performSync(false, false, false, traceId);
          }
        }
      };
      await runSync();
    }
  }
  const SupabaseProvider = AppContainer.register(
    "supabase-provider",
    new SupabaseProviderImpl()
  );
  class MergeEngine {
    static merge(localHistory, remoteHistory, localDetails, remoteDetails, preferRemote = false) {
      const historyMap = new Map();
      const detailMap = new Map();
      if (remoteHistory) {
        remoteHistory.forEach((r) => {
          if (!r.updated_at) r.updated_at = new Date(r.timestamp || 0).toISOString();
          historyMap.set(r.id, r);
        });
      }
      if (remoteDetails) {
        remoteDetails.forEach((d) => {
          if (!d.updated_at) d.updated_at = ( new Date(0)).toISOString();
          detailMap.set(d.id, d);
        });
      }
      for (const local of localHistory) {
        const remote = historyMap.get(local.id);
        if (!local.updated_at) local.updated_at = new Date(local.timestamp).toISOString();
        if (!remote) {
          historyMap.set(local.id, local);
        } else {
          const localTime = new Date(local.updated_at).getTime();
          const remoteTime = new Date(remote.updated_at).getTime();
          if (!preferRemote && localTime >= remoteTime) {
            historyMap.set(local.id, local);
          }
        }
      }
      for (const local of localDetails) {
        const remote = detailMap.get(local.id);
        if (!local.updated_at) local.updated_at = ( new Date(0)).toISOString();
        if (!remote) {
          detailMap.set(local.id, local);
        } else {
          const localTime = new Date(local.updated_at).getTime();
          const remoteTime = new Date(remote.updated_at).getTime();
          if (!preferRemote && localTime >= remoteTime) {
            detailMap.set(local.id, local);
          }
        }
      }
      return {
        history: Array.from(historyMap.values()),
        details: Array.from(detailMap.values())
      };
    }
  }
  const log$c = Logger.scope("WebDAV");
  class WebDAVClient {
    static getAuthHeader() {
      const { webdavUser, webdavPass } = State.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(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;
        try {
          const res = await this.request("PROPFIND", currentPath, null, { Depth: "0" });
          if (res.status === 404) {
            log$c.debug(`Creating directory: ${currentPath}`);
            await this.request("MKCOL", currentPath);
          }
        } catch (e) {
          log$c.error(`EnsureDirectory failed: ${currentPath}`, e);
        }
      }
    }
    static async fetchFile(url, isGzip) {
      try {
        const res = await this.request("GET", url, null, {}, isGzip ? "blob" : "text");
        if (res.status === 200) {
          const etag = res.responseHeaders.match(/etag:\s*(.*)/i)?.[1]?.replace(/"/g, "").trim();
          let text = res.responseText;
          if (isGzip && res.response) {
            text = await GzipService.decompress(res.response);
          }
          return { data: JSON.parse(text), etag };
        }
      } catch (e) {
        log$c.debug(`Fetch failed for ${url}`, e);
      }
      return null;
    }
  }
  const log$b = Logger.scope("WebDAV");
  class WebDAVProviderImpl {
    constructor() {
      this.name = "webdav";
      this.isSyncing = false;
    }
    onInit() {
    }
    async runSync(isManual = false, forceRefresh = false, retryOnConflict = true, preferRemote = false, traceId) {
      if (this.isSyncing) return;
      const now = Date.now();
      const lastLock = GM_getValue(STORAGE_KEYS.WEBDAV_SYNC_LOCK, 0);
      if (now - lastLock < 1e4) {
        if (isManual) CoreEvents.emit(AppEvents.SHOW_TOAST, { message: t("alertSyncLockActive"), type: "warn" });
        return;
      }
      GM_setValue(STORAGE_KEYS.WEBDAV_SYNC_LOCK, now);
      const { webdavUrl, webdavPath } = State.proxy;
      if (!webdavUrl || !webdavPath) return;
      Logger.group("WebDAV", `Sync (Force: ${forceRefresh}, CloudFirst: ${preferRemote})`, traceId);
      this.isSyncing = true;
      State.proxy.syncStatus = SYNC_STATUS.SYNCING;
      if (isManual) CoreEvents.emit(AppEvents.SHOW_TOAST, { message: t("labelSyncing"), type: "info" });
      try {
        await WebDAVClient.ensureDirectory(webdavUrl, webdavPath);
        const baseUrl = webdavUrl.replace(/\/$/, "");
        const targetUrl = `${baseUrl}/${webdavPath}${GzipService.isSupported() ? ".gz" : ""}`;
        const useGzip = GzipService.isSupported();
        const headRes = await WebDAVClient.request("HEAD", targetUrl);
        const remoteETag = headRes.status === 200 ? headRes.responseHeaders.match(/etag:\s*(.*)/i)?.[1]?.replace(/"/g, "").trim() : void 0;
        const lastEtag = GM_getValue(STORAGE_KEYS.WEBDAV_LAST_ETAG);
        const dirtyCount = await Repository.db.history.where("sync_dirty").equals(1).count();
        if (!preferRemote && !forceRefresh && remoteETag && lastEtag === remoteETag && dirtyCount === 0) {
          State.proxy.syncStatus = SYNC_STATUS.SUCCESS;
          if (isManual)
            CoreEvents.emit(AppEvents.SHOW_TOAST, { message: t("alertAlreadyUpToDate"), type: "info" });
          return;
        }
        let remoteData;
        if (forceRefresh || preferRemote || !lastEtag || lastEtag !== remoteETag || headRes.status === 404) {
          const fetched = await WebDAVClient.fetchFile(targetUrl, useGzip);
          if (fetched?.data) {
            const d = fetched.data;
            if (d.checksum && await CryptoService.calculateChecksum(d.history) !== d.checksum) {
              throw new Error("Remote checksum mismatch");
            }
            remoteData = d;
          }
        }
        const localHistory = await Repository.db.history.toArray();
        const localDetails = await Repository.db.itemDetails.toArray();
        const { history: mergedHistory, details: mergedDetails } = MergeEngine.merge(
          localHistory,
          remoteData?.history,
          localDetails,
          remoteData?.itemDetails,
          preferRemote
        );
        const payload = {
          version: 2,
          updated_at: ( new Date()).toISOString(),
          history: mergedHistory,
          itemDetails: mergedDetails,
          checksum: await CryptoService.calculateChecksum(mergedHistory)
        };
        const putHeaders = {
          "Content-Type": useGzip ? "application/gzip" : "application/json"
        };
        if (remoteETag) putHeaders["If-Match"] = `"${remoteETag.replace(/"/g, "")}"`;
        const body = useGzip ? await GzipService.compress(JSON.stringify(payload)) : JSON.stringify(payload);
        const res = await WebDAVClient.request("PUT", targetUrl, body, putHeaders);
        if (res.status >= 200 && res.status < 300) {
          const newEtag = res.responseHeaders.match(/etag:\s*(.*)/i)?.[1]?.replace(/"/g, "").trim() || remoteETag;
          if (newEtag) GM_setValue(STORAGE_KEYS.WEBDAV_LAST_ETAG, newEtag);
          await Repository.db.transaction("rw", Repository.db.history, Repository.db.itemDetails, async () => {
            await Repository.db.history.where("sync_dirty").equals(1).modify({ sync_dirty: 0, retry_count: 0 });
            await Repository.db.history.bulkPut(mergedHistory.map((h2) => ({ ...h2, sync_dirty: 0 })));
            await Repository.db.itemDetails.bulkPut(mergedDetails);
          });
          await HistoryService.load();
          State.proxy.syncStatus = SYNC_STATUS.SUCCESS;
          State.proxy.lastSyncTime = ( new Date()).toISOString();
          if (isManual)
            CoreEvents.emit(AppEvents.SHOW_TOAST, { message: t("alertWebDAVSyncSuccess"), type: "success" });
        } else if (res.status === 412 && retryOnConflict) {
          this.isSyncing = false;
          return this.runSync(isManual, true, false, false, traceId);
        } else {
          throw new Error(`WebDAV PUT failed: ${res.status}`);
        }
      } catch (e) {
        const err = e;
        log$b.error("Sync error", e, traceId);
        State.proxy.syncStatus = SYNC_STATUS.ERROR;
        if (isManual)
          CoreEvents.emit(AppEvents.SHOW_TOAST, {
            message: t("alertWebDAVSyncError") + (err.message || ""),
            type: "error"
          });
      } finally {
        this.isSyncing = false;
        GM_setValue(STORAGE_KEYS.WEBDAV_SYNC_LOCK, 0);
        Logger.groupEnd();
      }
    }
    async test() {
      const { webdavUrl } = State.proxy;
      if (!webdavUrl) throw new Error("URL is empty");
      return WebDAVClient.request("PROPFIND", webdavUrl.replace(/\/$/, "") + "/", null, { Depth: "0" });
    }
    async logout() {
      [
        STORAGE_KEYS.WEBDAV_URL,
        STORAGE_KEYS.WEBDAV_USER,
        STORAGE_KEYS.WEBDAV_PASS,
        STORAGE_KEYS.WEBDAV_PATH,
        STORAGE_KEYS.WEBDAV_LAST_ETAG
      ].forEach((k) => GM_deleteValue(k));
    }
    async performSync(isManual = false, forceRefresh = false, preferRemote = false, traceId) {
      if (forceRefresh || preferRemote) GM_deleteValue(STORAGE_KEYS.WEBDAV_LAST_ETAG);
      await RetryManager.executeWithRetry(
        () => this.runSync(isManual, forceRefresh, true, preferRemote, traceId),
        RetryManager.configs.sync
      );
    }
  }
  const WebDAVProvider = AppContainer.register("webdav-provider", new WebDAVProviderImpl());
  const log$a = Logger.scope("Sync");
  class SyncServiceImplementation {
    constructor() {
      this.syncTimer = null;
    }
    onBootstrap() {
      log$a.debug("Starting auto-sync scheduler");
      setTimeout(
        () => this.performSync(false).catch((e) => {
          log$a.warn("Initial auto-sync failed", e);
        }),
        TIMING.SYNC_INIT_DELAY
      );
      CoreEvents.on(AppEvents.HISTORY_CHANGED, () => {
        this.requestSync();
      });
    }
    getProvider() {
      const mode = State.proxy.syncMode;
      if (mode === "webdav") return WebDAVProvider;
      if (mode === "supabase") return SupabaseProvider;
      return null;
    }
    set onProgress(val) {
      SupabaseProvider.onProgress = val;
    }
    async login(email, password) {
      return await SupabaseProvider.login(email, password);
    }
    async signup(email, password) {
      return await SupabaseProvider.signup(email, password);
    }
    async logout(silent = false) {
      const provider = this.getProvider();
      if (provider) {
        await provider.logout();
      } else {
        SupabaseProvider.logout(silent);
      }
      State.proxy.syncMode = "none";
      State.proxy.syncStatus = SYNC_STATUS.IDLE;
      GM_setValue(STORAGE_KEYS.LAST_SYNC_TS, UI_CONSTANTS.DEFAULT_TIMESTAMP);
    }
    async testWebDAV() {
      return await WebDAVProvider.test();
    }
    requestSync() {
      const interval = State.proxy.syncInterval;
      const mode = State.proxy.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`);
        if (this.syncTimer) clearTimeout(this.syncTimer);
        this.syncTimer = setTimeout(
          () => this.performSync().catch((e) => log$a.error("Debounced sync failed", e)),
          TIMING.SYNC_DEBOUNCE_MS
        );
        return;
      }
      const lastAutoSync = GM_getValue(STORAGE_KEYS.LAST_AUTO_SYNC_TS, 0);
      const now = Date.now();
      const minInterval = interval * 60 * 1e3;
      if (now - lastAutoSync < minInterval) return;
      Logger.info(
        "SyncService",
        `Sync requested (interval: ${interval}min), executing in ${TIMING.SYNC_DEBOUNCE_MS}ms`
      );
      if (this.syncTimer) clearTimeout(this.syncTimer);
      this.syncTimer = setTimeout(() => {
        GM_setValue(STORAGE_KEYS.LAST_AUTO_SYNC_TS, Date.now());
        this.performSync().catch((e) => log$a.error("Scheduled sync failed", e));
      }, TIMING.SYNC_DEBOUNCE_MS);
    }
    async performSync(isManual = false, forceRefresh = false, preferRemote = false) {
      const traceId = Logger.traceId;
      try {
        const provider = this.getProvider();
        if (!provider) return;
        await navigator.locks.request("fc2_sync_lock", { ifAvailable: true }, async (lock) => {
          if (!lock) {
            if (isManual) {
              log$a.info("Manual sync skipped: lock held by another tab");
              Toast.show(t("alertSyncLocked") || "Sync already in progress in another tab", "warn");
            } else {
              log$a.debug("Auto-sync skipped: lock held by another tab");
            }
            return;
          }
          log$a.info(`Starting sync (manual: ${isManual}, force: ${forceRefresh})`, traceId);
          await provider.performSync(isManual, forceRefresh, preferRemote, traceId);
        });
      } catch (error) {
        log$a.error("Sync failed", error, traceId);
        State.proxy.syncStatus = SYNC_STATUS.ERROR;
      }
    }
    async forceFullSync() {
      if (!confirm(t("alertPushAllQuery"))) return;
      await Repository.db.history.toCollection().modify({ sync_dirty: 1 });
      GM_setValue(STORAGE_KEYS.LAST_SYNC_TS, UI_CONSTANTS.DEFAULT_TIMESTAMP);
      return await this.performSync(true, true);
    }
    async forcePullSync() {
      if (!confirm(t("alertPullAllQuery"))) return;
      GM_setValue(STORAGE_KEYS.LAST_SYNC_TS, UI_CONSTANTS.DEFAULT_TIMESTAMP);
      return await this.performSync(true, true, true);
    }
  }
  const SyncService = AppContainer.register("sync-service", new SyncServiceImplementation());
  const log$9 = Logger.scope("Cleanup");
  class CleanupServiceImplementation {
    constructor() {
      this.RETENTION_PERIOD = 30 * 24 * 60 * 60 * 1e3;
    }
onBootstrap() {
      log$9.debug("Scheduling maintenance");
      setTimeout(() => {
        this.runAllGC().catch((e) => log$9.error("Maintenance failed", e));
      }, 5e3);
    }
    async runAllGC() {
      Logger.group("Cleanup", "System maintenance");
      try {
        await this.runTombstoneGC();
        await Repository.runGC();
        log$9.info("Maintenance complete");
      } finally {
        Logger.groupEnd();
      }
    }
    async runTombstoneGC() {
      try {
        const thresholdDate = new Date(Date.now() - this.RETENTION_PERIOD).toISOString();
        const deletedCount = await Repository.db.history.where("is_deleted").equals(1).and((item) => item.updated_at < thresholdDate).delete();
        if (deletedCount > 0) {
          log$9.info(`Purged ${deletedCount} tombstone records`);
        }
      } catch (e) {
        log$9.error("Tombstone GC failed", e);
      }
    }
  }
  AppContainer.register("cleanup-service", new CleanupServiceImplementation());
  const log$8 = Logger.scope("Preview");
  class PreviewServiceImplementation {
    constructor() {
      this.cache = new Map();
      this.maxCacheSize = CACHE.PREVIEW_MAX_SIZE;
      this.preloadQueue = new Set();
    }
    async onBootstrap() {
      log$8.debug("Binding global preview events");
      this.initGlobalEvents();
    }
    registerContainer(container2) {
      const mode = State.proxy.previewMode;
      if (mode === "static") return;
      const selector = `.${Config.CLASSES.processedCard}`;
      const isTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0;
      if (isTouch) {
        container2.addEventListener(
          "click",
          (e) => {
            const imgEl = e.target.closest(`img.${Config.CLASSES.staticPreview}`);
            if (imgEl) {
              const card2 = imgEl.closest(selector);
              if (card2 && !card2.querySelector("video")) {
                e.preventDefault();
                e.stopPropagation();
                this._loadVideoProgressive(card2);
              }
            }
          },
          true
        );
      } else if (mode === "hover") {
        container2.addEventListener(
          "mouseenter",
          (e) => {
            const card2 = e.target.closest(selector);
            if (card2) {
              this._loadVideoProgressive(card2);
              this._smartPreload(card2);
            }
          },
          true
        );
      }
    }
    initGlobalEvents() {
      const mode = State.proxy.previewMode;
      if (mode === "static") return;
      this.registerContainer(document.body);
      window.addEventListener("beforeunload", () => this.clearCache());
    }

_loadVideoProgressive(card2) {
      const cont = card2.querySelector(`.${Config.CLASSES.videoPreviewContainer}`);
      const img = cont?.querySelector(`img.${Config.CLASSES.staticPreview}`);
      if (!cont || !img) return;
      const { id, type, previewSlug } = card2.dataset;
      const url = this._getPreviewUrl(id, type, previewSlug);
      const cached = this.cache.get(id);
      if (cached && cached.element instanceof HTMLVideoElement) {
        const video2 = cached.element;
        if (video2.parentNode !== cont) {
          if (video2.parentNode) video2.remove();
          cont.appendChild(video2);
        }
        cont.classList.add("fc2-preview-active");
        video2.classList.remove(Config.CLASSES.hidden);
        video2.classList.add("fc2-reveal-content");
        img.classList.add(Config.CLASSES.hidden);
        video2.play().catch(() => {
        });
        cached.timestamp = Date.now();
        this._attachWarmCleanup(card2, video2, img, cont);
        return;
      }
      const existingVideo = cont.querySelector("video");
      if (existingVideo) {
        cont.classList.add("fc2-preview-active");
        existingVideo.classList.remove(Config.CLASSES.hidden);
        img.classList.add(Config.CLASSES.hidden);
        existingVideo.play().catch(() => {
        });
        this._attachWarmCleanup(card2, existingVideo, img, cont);
        return;
      }
      this._showLoadingIndicator(cont);
      const video = this._createVideoElement(url);
      cont.appendChild(video);
      let isStillHovered = true;
      const isTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0;
      if (!isTouch) {
        this._attachWarmCleanup(card2, video, img, cont, () => {
          isStillHovered = false;
        });
      }
      this._attachLoadingEventsWithCheck(video, cont, img, () => isStillHovered, id);
    }
    _attachWarmCleanup(card2, video, img, cont, onCleanup) {
      const cleanup = () => {
        if (onCleanup) onCleanup();
        cont.classList.remove("fc2-preview-active");
        if (video.isConnected) {
          video.pause();
          video.classList.add(Config.CLASSES.hidden);
          video.classList.remove("fc2-reveal-content");
        }
        img.classList.remove(Config.CLASSES.hidden);
        this._hideLoadingIndicator(cont);
        const id = card2.dataset.id;
        if (id) this._cachePreview(id, video);
      };
      card2.addEventListener("mouseleave", cleanup, { once: true });
    }
    _createVideoElement(url) {
      return h("video", {
        src: url,
        autoplay: true,
        loop: true,
        muted: true,
        playsInline: true,
        preload: "auto",
        className: `${Config.CLASSES.previewElement} ${Config.CLASSES.hidden}`
      });
    }
    _attachLoadingEventsWithCheck(video, cont, img, checkHover, id) {
      video.addEventListener("progress", () => {
        if (video.buffered.length > 0 && checkHover()) {
          const percent = video.buffered.end(0) / video.duration * 100;
          this._updateLoadingProgress(cont, percent);
        }
      });
      video.addEventListener(
        "playing",
        () => {
          if (!checkHover()) {
            video.pause();
            this._cachePreview(id, video);
            return;
          }
          requestAnimationFrame(() => {
            if (checkHover()) {
              cont.classList.add("fc2-preview-active");
              video.classList.remove(Config.CLASSES.hidden);
              video.classList.add("fc2-reveal-content");
              img.classList.add(Config.CLASSES.hidden);
              this._hideLoadingIndicator(cont);
            } else {
              video.pause();
              this._cachePreview(id, video);
            }
          });
        },
        { once: true }
      );
      video.play().catch(() => {
      });
      video.addEventListener("error", () => {
        if (checkHover()) {
          this._hideLoadingIndicator(cont);
          this._showErrorIndicator(cont);
        }
      });
    }
    _showLoadingIndicator(cont) {
      if (cont.querySelector(".preview-loading")) return;
      const loader = h(
        "div",
        {
          className: "preview-loading",
          style: "position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10;"
        },
        h("div", {
          className: "preview-spinner",
          style: "width: 40px; height: 40px; border: 3px solid rgba(255,255,255,0.3); border-top-color: #fff; border-radius: 50%; animation: fc2-spin 0.8s linear infinite;"
        }),
        h(
          "div",
          {
            className: "preview-progress",
            style: "margin-top: 10px; color: #fff; font-size: 12px; text-align: center;"
          },
          "加载中..."
        )
      );
      cont.appendChild(loader);
    }
    _updateLoadingProgress(cont, percent) {
      const progress = cont.querySelector(".preview-progress");
      if (progress) progress.textContent = `${Math.round(percent)}%`;
    }
    _hideLoadingIndicator(cont) {
      cont.querySelector(".preview-loading")?.remove();
    }
    _showErrorIndicator(cont) {
      const error = h(
        "div",
        {
          className: "preview-error",
          style: "position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #f87171; font-size: 12px; text-align: center; z-index: 10;"
        },
        h("span", {
          className: "fc2-icon",
          innerHTML: IconTriangleExclamation,
          style: "font-size: 24px; margin-bottom: 5px; display: inline-block;"
        }),
        h("div", {}, "预览加载失败")
      );
      cont.appendChild(error);
      setTimeout(() => error.remove(), TIMING.PREVIEW_ERROR_DELAY);
    }
    _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 _smartPreload(currentCard) {
      const cards = Array.from(document.querySelectorAll(`.${Config.CLASSES.processedCard}`));
      const currentIndex = cards.indexOf(currentCard);
      if (currentIndex === -1) return;
      const toPreload = [cards[currentIndex + 1], cards[currentIndex - 1]].filter(Boolean);
      for (const card2 of toPreload) {
        const { id } = card2.dataset;
        if (id && !this.preloadQueue.has(id)) {
          if (this.preloadQueue.size >= CACHE.PREVIEW_PRELOAD_LIMIT) {
            const first = this.preloadQueue.values().next().value;
            if (first) this.preloadQueue.delete(first);
          }
          this.preloadQueue.add(id);
          this._preloadVideo(card2);
        }
      }
    }
    _preloadVideo(card2) {
      if (card2.querySelector("video")) return;
      const { id, type, previewSlug } = card2.dataset;
      const url = this._getPreviewUrl(id, type, previewSlug);
      const link = document.createElement("link");
      link.rel = "preload";
      link.as = "video";
      link.href = url;
      document.head.appendChild(link);
      setTimeout(() => {
        if (link.parentNode) link.remove();
      }, TIMING.LINK_PRELOAD_TIMEOUT);
    }
    _cachePreview(id, element) {
      if (this.cache.has(id)) {
        this.cache.get(id).timestamp = Date.now();
        return;
      }
      if (this.cache.size >= this.maxCacheSize) {
        const entries = Array.from(this.cache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp);
        const oldest = entries[0];
        if (oldest) {
          const oldestId = oldest[0];
          const oldestItem = oldest[1];
          if (oldestItem.element instanceof HTMLVideoElement) {
            oldestItem.element.pause();
            oldestItem.element.src = "";
            oldestItem.element.load();
            oldestItem.element.remove();
          }
          this.cache.delete(oldestId);
        }
      }
      this.cache.set(id, { url: element.src, element, timestamp: Date.now() });
    }
    clearCache() {
      this.cache.forEach((item) => {
        if (item.element instanceof HTMLVideoElement) {
          item.element.pause();
          item.element.src = "";
          item.element.load();
          item.element.remove();
        }
      });
      this.cache.clear();
      log$8.debug("Cache cleared");
    }
  }
  AppContainer.register("preview-service", new PreviewServiceImplementation());
  const log$7 = Logger.scope("Menu");
  class MenuServiceImplementation {
    constructor() {
      this.menuIds = [];
    }
    onBootstrap() {
      log$7.debug("Registering UserScript menus");
      this.register();
    }
    register() {
      if (typeof GM_registerMenuCommand === "undefined") return;
      this.menuIds.forEach((id) => {
        try {
          GM_unregisterMenuCommand(id);
        } catch {
        }
      });
      this.menuIds = [];
      const settingsId = GM_registerMenuCommand(t("menuOpenSettings"), () => {
        CoreEvents.emit(AppEvents.OPEN_SETTINGS, {
});
      });
      this.menuIds.push(settingsId);
    }
  }
  const MenuService = AppContainer.register("menu-service", new MenuServiceImplementation());
  const log$6 = Logger.scope("Migration");
  class MigrationServiceImplementation {
    onBootstrap() {
      log$6.debug("Checking for pending migrations");
      this.run().catch((e) => log$6.error("Run failed", e));
    }
    async run() {
      const traceId = Logger.traceId;
      const currentVersion = Storage.get("migration_version", 0);
      const TARGET_VERSION = 1;
      if (currentVersion >= TARGET_VERSION) {
        return;
      }
      Logger.group("Migration", `v${currentVersion} -> v${TARGET_VERSION}`, traceId);
      try {
        await this.migrateHistory(traceId);
        await this.migrateCache(traceId);
        Storage.set("migration_version", TARGET_VERSION);
        log$6.info("Migration completed", void 0, traceId);
      } catch (e) {
        log$6.error("Migration failed", e, traceId);
      } finally {
        Logger.groupEnd();
      }
    }
    async migrateHistory(traceId) {
      const oldKey = "history_v1";
      const oldDataStr = Storage.get(oldKey, null);
      if (!oldDataStr) {
        log$6.debug("No old history found", void 0, traceId);
        return;
      }
      try {
        let oldData = oldDataStr;
        if (typeof oldDataStr === "string") {
          try {
            oldData = JSON.parse(oldDataStr);
          } catch {
            log$6.warn("Failed to parse history JSON", void 0, traceId);
          }
        }
        if (!Array.isArray(oldData)) {
          log$6.warn("Invalid history format (not array)", typeof oldData, traceId);
          return;
        }
        log$6.info(`Found ${oldData.length} raw history items`, void 0, traceId);
        const now = ( new Date()).toISOString();
        const validItemsMap = new Map();
        for (const item of oldData) {
          try {
            let id = "";
            let timestamp = Date.now();
            if (typeof item === "number") {
              id = String(item);
            } else if (typeof item === "string") {
              id = item.trim();
            } else if (typeof item === "object" && item !== null) {
              if (item.id) id = String(item.id).trim();
              if (typeof item.timestamp === "number" && !isNaN(item.timestamp) && item.timestamp > 0) {
                timestamp = item.timestamp;
              }
            }
            if (!id) continue;
            if (validItemsMap.has(id)) {
              const existing = validItemsMap.get(id);
              if (timestamp > existing.timestamp) {
                existing.timestamp = timestamp;
              }
            } else {
              validItemsMap.set(id, {
                id,
                timestamp,
                status: "watched",
                updated_at: now,
                is_deleted: 0,
                sync_dirty: 1
              });
            }
          } catch {
            log$6.warn("Skipping malformed history item", item, traceId);
          }
        }
        const dbItems = Array.from(validItemsMap.values());
        if (dbItems.length > 0) {
          await Repository.db.history.bulkPut(dbItems);
          log$6.info(`Migrated ${dbItems.length} unique history items`, void 0, traceId);
        }
      } catch (e) {
        log$6.error("Critical error migrating history", e, traceId);
      }
    }
    async migrateCache(traceId) {
      const oldKey = "magnet_cache_v1";
      const oldDataStr = Storage.get(oldKey, null);
      if (!oldDataStr) {
        return;
      }
      try {
        let oldData = oldDataStr;
        if (typeof oldDataStr === "string") {
          try {
            oldData = JSON.parse(oldDataStr);
          } catch {
          }
        }
        if (typeof oldData !== "object" || oldData === null) return;
        const entries = Object.entries(oldData);
        log$6.info(`Found ${entries.length} cache items to migrate`, void 0, traceId);
        const validItems = [];
        const now = Date.now();
        for (const [key, val] of entries) {
          try {
            const id = String(key).trim();
            if (!id) continue;
            const valueObj = val;
            if (!valueObj || typeof valueObj.v !== "string") continue;
            const magnetUrl = valueObj.v;
            if (!magnetUrl.startsWith("magnet:?")) continue;
            let timestamp = now;
            if (typeof valueObj.t === "number" && !isNaN(valueObj.t) && valueObj.t > 0) {
              timestamp = valueObj.t;
            }
            validItems.push({
              id,
              value: magnetUrl,
              timestamp
            });
          } catch {
          }
        }
        if (validItems.length > 0) {
          await Repository.db.cache.bulkPut(validItems);
          log$6.info(`Migrated ${validItems.length} cache items`, void 0, traceId);
        }
      } catch (e) {
        log$6.error("Error migrating cache", e, traceId);
      }
    }
  }
  AppContainer.register("migration-service", new MigrationServiceImplementation());
  const log$5 = Logger.scope("Backup");
  const _BackupServiceImplementation = class _BackupServiceImplementation {
    async onInit() {
      log$5.debug("Service initialized");
    }
async exportData() {
      const history2 = await Repository.history.getAll();
      const details = await Repository.details.getAll();
      const { syncStatus: _syncStatus, ...persistentSettings } = State.proxy;
      const data = {
        appName: SCRIPT_INFO.NAME,
        version: 3,
timestamp: Date.now(),
        settings: persistentSettings,
        history: history2,
        details
      };
      try {
        const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        const dateStr = ( new Date()).toISOString().slice(0, 10);
        a.href = url;
        a.download = `fc2_enhanced_backup_${dateStr}.json`;
        a.click();
        setTimeout(() => {
          URL.revokeObjectURL(url);
          a.remove();
        }, 100);
        log$5.info("Data exported successfully");
        return true;
      } catch (err) {
        log$5.error("Export failed", err);
        return false;
      }
    }
async importData(file) {
      return new Promise((resolve) => {
        const reader = new FileReader();
        reader.onload = async (e) => {
          try {
            const content = e.target?.result;
            const data = JSON.parse(content);
            if (!data.settings || !data.history) {
              log$5.error("Invalid backup file format");
              resolve(false);
              return;
            }
            log$5.debug("Importing settings...");
            for (const [key, value] of Object.entries(data.settings)) {
              if (_BackupServiceImplementation.ALLOWED_SETTINGS_KEYS.has(key)) {
                State.proxy[key] = value;
              } else {
                log$5.warn(`Skipping unknown settings key during import: ${key}`);
              }
            }
            log$5.debug("Importing history & metadata...");
            await Repository.db.transaction(
              "rw",
              [Repository.db.history, Repository.db.itemDetails],
              async () => {
                if (Array.isArray(data.history)) {
                  await Repository.db.history.bulkPut(data.history);
                }
                if (Array.isArray(data.details)) {
                  const validDetails = data.details.filter((d) => d.id);
                  if (validDetails.length > 0) {
                    await Repository.db.itemDetails.bulkPut(validDetails);
                  }
                }
              }
            );
            log$5.info(`Imported ${data.history.length} history items and metadata`);
            resolve(true);
          } catch (err) {
            log$5.error("Import failed during parsing/saving", err);
            resolve(false);
          }
        };
        reader.onerror = () => resolve(false);
        reader.readAsText(file);
      });
    }
  };
  _BackupServiceImplementation.ALLOWED_SETTINGS_KEYS = new Set([
    "previewMode",
    "hideNoMagnet",
    "hideCensored",
    "enableHistory",
    "hideViewed",
    "enableFollows",
    "loadExtraPreviews",
    "enableQuickBar",
    "showViewedBtn",
    "showIdBadge",
    "enableMagnets",
    "enableExternalLinks",
    "enableActressName",
    "hideBlocked",
    "hideUnwanted",
    "language",
    "syncMode",
    "syncInterval",
    "replaceFc2Covers",
    "enabledPortals",
    "userGridColumns",
    "debugMode",
    "customFolders",
    "collectionCardWidth"
  ]);
  let BackupServiceImplementation = _BackupServiceImplementation;
  const BackupService = AppContainer.register("backup-service", new BackupServiceImplementation());
  const scriptRel = (function detectScriptRel() {
    const relList = typeof document !== "undefined" && document.createElement("link").relList;
    return relList && relList.supports && relList.supports("modulepreload") ? "modulepreload" : "preload";
  })();
  const assetsURL = function(dep) {
    return "/" + dep;
  };
  const seen = {};
  const __vitePreload = function preload(baseModule, deps, importerUrl) {
    let promise = Promise.resolve();
    if (deps && deps.length > 0) {
      let allSettled2 = function(promises) {
        return Promise.all(
          promises.map(
            (p) => Promise.resolve(p).then(
              (value) => ({ status: "fulfilled", value }),
              (reason) => ({ status: "rejected", reason })
            )
          )
        );
      };
      document.getElementsByTagName("link");
      const cspNonceMeta = document.querySelector(
        "meta[property=csp-nonce]"
      );
      const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce");
      promise = allSettled2(
        deps.map((dep) => {
          dep = assetsURL(dep);
          if (dep in seen) return;
          seen[dep] = true;
          const isCss = dep.endsWith(".css");
          const cssSelector = isCss ? '[rel="stylesheet"]' : "";
          if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) {
            return;
          }
          const link = document.createElement("link");
          link.rel = isCss ? "stylesheet" : scriptRel;
          if (!isCss) {
            link.as = "script";
          }
          link.crossOrigin = "";
          link.href = dep;
          if (cspNonce) {
            link.setAttribute("nonce", cspNonce);
          }
          document.head.appendChild(link);
          if (isCss) {
            return new Promise((res, rej) => {
              link.addEventListener("load", res);
              link.addEventListener(
                "error",
                () => rej(new Error(`Unable to preload CSS for ${dep}`))
              );
            });
          }
        })
      );
    }
    function handlePreloadError(err) {
      const e = new Event("vite:preloadError", {
        cancelable: true
      });
      e.payload = err;
      window.dispatchEvent(e);
      if (!e.defaultPrevented) {
        throw err;
      }
    }
    return promise.then((res) => {
      for (const item of res || []) {
        if (item.status !== "rejected") continue;
        handlePreloadError(item.reason);
      }
      return baseModule().catch(handlePreloadError);
    });
  };
  const getTabsConfig = () => [
    {
      id: "dashboard",
      title: t("tabDashboard"),
      icon: IconHouse,
      render: async (shadow, switchTab) => {
        const { renderDashboardTab: renderDashboardTab2 } = await __vitePreload(async () => {
          const { renderDashboardTab: renderDashboardTab3 } = await Promise.resolve().then(() => DashboardTab);
          return { renderDashboardTab: renderDashboardTab3 };
        }, void 0 );
        return renderDashboardTab2(shadow, switchTab);
      },
      onUnmount: async () => {
        const { onUnmount: onUnmount2 } = await __vitePreload(async () => {
          const { onUnmount: onUnmount3 } = await Promise.resolve().then(() => DashboardTab);
          return { onUnmount: onUnmount3 };
        }, void 0 );
        onUnmount2();
      }
    },
    {
      id: "collection",
      title: t("tabCollection"),
      icon: IconStar,
      render: async (shadow) => {
        const { renderCollectionTab: renderCollectionTab2 } = await __vitePreload(async () => {
          const { renderCollectionTab: renderCollectionTab3 } = await Promise.resolve().then(() => CollectionTab);
          return { renderCollectionTab: renderCollectionTab3 };
        }, void 0 );
        return renderCollectionTab2(shadow);
      },
      onUnmount: async () => {
        const { onUnmount: onUnmount2 } = await __vitePreload(async () => {
          const { onUnmount: onUnmount3 } = await Promise.resolve().then(() => CollectionTab);
          return { onUnmount: onUnmount3 };
        }, void 0 );
        onUnmount2();
      },
      isFullWidth: true
    },
    {
      id: "data",
      title: t("tabData"),
      icon: IconDatabase,
      render: async (shadow, switchTab) => {
        const { renderDataTab: renderDataTab2 } = await __vitePreload(async () => {
          const { renderDataTab: renderDataTab3 } = await Promise.resolve().then(() => DataTab);
          return { renderDataTab: renderDataTab3 };
        }, void 0 );
        return renderDataTab2(shadow, switchTab);
      },
      onUnmount: async () => {
        const { onUnmount: onUnmount2 } = await __vitePreload(async () => {
          const { onUnmount: onUnmount3 } = await Promise.resolve().then(() => DataTab);
          return { onUnmount: onUnmount3 };
        }, void 0 );
        onUnmount2();
      }
    },
    {
      id: "settings",
      title: t("tabSettings"),
      icon: IconSliders,
      render: async () => {
        const { renderSettingsTab: renderSettingsTab2 } = await __vitePreload(async () => {
          const { renderSettingsTab: renderSettingsTab3 } = await Promise.resolve().then(() => SettingsTab);
          return { renderSettingsTab: renderSettingsTab3 };
        }, void 0 );
        return renderSettingsTab2();
      },
      onUnmount: async () => {
        const { onUnmount: onUnmount2 } = await __vitePreload(async () => {
          const { onUnmount: onUnmount3 } = await Promise.resolve().then(() => SettingsTab);
          return { onUnmount: onUnmount3 };
        }, void 0 );
        onUnmount2();
      }
    },
    {
      id: "debug",
      title: t("labelTechnicalLogs"),
      icon: IconBolt,
      render: async () => {
        const { renderDebugTab: renderDebugTab2 } = await __vitePreload(async () => {
          const { renderDebugTab: renderDebugTab3 } = await Promise.resolve().then(() => DebugTab);
          return { renderDebugTab: renderDebugTab3 };
        }, void 0 );
        return renderDebugTab2();
      },
      onUnmount: async () => {
        const { onUnmount: onUnmount2 } = await __vitePreload(async () => {
          const { onUnmount: onUnmount3 } = await Promise.resolve().then(() => DebugTab);
          return { onUnmount: onUnmount3 };
        }, void 0 );
        onUnmount2();
      }
    },
    {
      id: "about",
      title: t("tabAbout"),
      icon: IconCircleInfo,
      render: async () => {
        const { renderAboutTab: renderAboutTab2 } = await __vitePreload(async () => {
          const { renderAboutTab: renderAboutTab3 } = await Promise.resolve().then(() => AboutTab);
          return { renderAboutTab: renderAboutTab3 };
        }, void 0 );
        return renderAboutTab2();
      }
    }
  ];
  const log$4 = Logger.scope("Settings");
  const mkIcon = (svg) => UIUtils.icon(svg);
  const createLoadingOverlay = () => h(
    "div",
    { className: "fc2-loading-overlay" },
    h("div", { className: "fc2-loading-spinner" }),
    h("div", { className: "fc2-loading-text" }, `${t("labelLoading")}...`)
  );
  const createErrorState = (tabId) => h("div", { className: "fc2-error-state" }, `${t("labelError")}: ${tabId}`);
  const SettingsPanel = (() => {
    let panel = null;
    let shadow = null;
    let currentTabId = "dashboard";
    let isSwitching = false;
    const tabCache = new Map();
    const tabsConfig = getTabsConfig();
    const SettingsPanelOverlay = { close: () => hide() };
    const hide = (e) => {
      if (e) {
        e.preventDefault();
        e.stopPropagation();
      }
      if (!panel) return;
      panel.classList.add("is-hidden");
      document.body.classList.remove("fc2-settings-open");
      document.removeEventListener("keydown", handleKeyDown);
      OverlayStack.remove(SettingsPanelOverlay);
      CoreEvents.emit(AppEvents.PANEL_CLOSED, {});
    };
    const handleKeyDown = (e) => {
      if (!panel || panel.classList.contains("is-hidden")) return;
      if ((e.ctrlKey || e.metaKey) && e.key === "s") {
        e.preventDefault();
        saveAndClose();
      }
      if (e.key === "Escape") {
        e.preventDefault();
        hide();
      }
    };
    const saveAndClose = () => {
      Toast.show(t("alertSettingsSaved"), "success");
      hide();
    };
    const getContentContainer = () => shadow?.getElementById(DOM_IDS.TAB_CONTENT) ?? null;
    const updateActiveTabButton = (tabId) => {
      shadow?.querySelectorAll(".fc2-enh-tab-btn").forEach((btn) => {
        btn.classList.toggle("active", btn.dataset.tab === tabId);
      });
    };
    const unmountTab = (tabDef) => {
      if (!tabDef?.onUnmount || !shadow) return;
      try {
        tabDef.onUnmount(shadow);
      } catch (err) {
        log$4.warn(`Tab unmount error: ${tabDef.id}`, err);
      }
    };
    const switchTab = async (tabId) => {
      if (isSwitching) return;
      if (currentTabId === tabId && tabCache.has(tabId)) {
        tabCache.delete(tabId);
      }
      const oldTabDef = tabsConfig.find((tc) => tc.id === currentTabId);
      const tabDef = tabsConfig.find((tc) => tc.id === tabId);
      if (!tabDef) return;
      isSwitching = true;
      const contentContainer = getContentContainer();
      if (!contentContainer) {
        log$4.error("Tab content container not found");
        isSwitching = false;
        return;
      }
      try {
        unmountTab(oldTabDef);
        const oldContent = contentContainer.querySelector(".fc2-tab-content-wrapper");
        if (oldContent) {
          oldContent.classList.add("fc2-leaving");
        }
        updateActiveTabButton(tabId);
        let content = tabCache.get(tabId);
        if (!content) {
          if (oldContent) {
            await new Promise((r) => setTimeout(r, TIMING.UI_TRANSITION_FAST));
          }
          contentContainer.textContent = "";
          contentContainer.appendChild(createLoadingOverlay());
          const element = await tabDef.render(shadow, switchTab);
          content = h(
            "div",
            {
              className: `fc2-tab-content-wrapper ${tabDef.isFullWidth ? "full-width" : ""}`,
              "data-tab": tabId
            },
            element
          );
          tabCache.set(tabId, content);
        }
        const enterDelay = oldContent && tabCache.has(tabId) ? TIMING.UI_TRANSITION_FAST : 0;
        setTimeout(() => {
          contentContainer.textContent = "";
          content.classList.remove("fc2-entering", "fc2-leaving");
          contentContainer.appendChild(content);
          requestAnimationFrame(() => {
            requestAnimationFrame(() => {
              content.classList.add("fc2-entering");
              currentTabId = tabId;
              isSwitching = false;
            });
          });
        }, enterDelay);
      } catch (err) {
        log$4.error(`Failed to render tab: ${tabId}`, err);
        isSwitching = false;
        contentContainer.textContent = "";
        contentContainer.appendChild(createErrorState(tabId));
        Toast.show(`${t("labelError")}: ${tabId}`, "error");
      }
    };
    const buildTabButtons = () => tabsConfig.map(
      (tab) => h(
        "button",
        {
          className: "fc2-enh-tab-btn",
          "data-tab": tab.id,
          onclick: () => switchTab(tab.id)
        },
        mkIcon(tab.icon),
        tab.title
      )
    );
    const buildPanel = () => {
      panel = h("div", {
        id: DOM_IDS.SETTINGS_HOST,
        className: "is-hidden"
      });
      const container2 = h("div", { id: DOM_IDS.SETTINGS_CONTAINER });
      shadow = container2.attachShadow({ mode: "open" });
      shadow.appendChild(h("style", {}, getComponentStyles(Config.CLASSES)));
      const header = h(
        "div",
        { className: "fc2-enh-settings-header" },
        h("h2", {}, t("managementCenter") || "管理中心"),
        h(
          "div",
          { className: "fc2-header-actions" },
          h(
            "button",
            { className: "close-btn", onclick: () => hide(), title: t("btnCancel") || "Close" },
            mkIcon(IconXmark)
          )
        )
      );
      const sidebar = h("div", { className: "fc2-enh-settings-tabs", id: "tab-buttons" }, ...buildTabButtons());
      const contentArea = h("div", {
        className: "fc2-enh-settings-content",
        id: DOM_IDS.TAB_CONTENT
      });
      const body = h("div", { className: "fc2-enh-settings-body" }, sidebar, contentArea);
      const footer = h(
        "div",
        { className: "fc2-enh-settings-footer" },
        h("button", { className: "fc2-enh-btn", onclick: (e) => hide(e) }, t("btnCancel")),
        h("button", { className: "fc2-enh-btn primary", onclick: () => saveAndClose() }, t("btnSave"))
      );
      const panelInner = h("div", { className: "fc2-enh-settings-panel has-shadow-isolate" }, header, body, footer);
      const backdrop = h("div", {
        className: "enh-modal-backdrop",
        onclick: () => hide()
      });
      shadow.append(backdrop, panelInner);
      panel.appendChild(container2);
      document.body.appendChild(panel);
    };
    const show = (activeTabId = "dashboard") => {
      if (!panel) {
        buildPanel();
      }
      if (panel.classList.contains("is-hidden") || currentTabId !== activeTabId) {
        panel.classList.remove("is-hidden");
        document.body.classList.add("fc2-settings-open");
        document.addEventListener("keydown", handleKeyDown);
        OverlayStack.push(SettingsPanelOverlay);
        CoreEvents.emit(AppEvents.PANEL_OPENED, {});
        switchTab(activeTabId);
      }
    };
    return {
      show,
      render: show,
      hide: () => hide(),
      clearCache: () => tabCache.clear(),
      switchTab,
      get currentTabId() {
        return currentTabId;
      }
    };
  })();
  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);
  });
  class DragManager {
    constructor(container2, trigger, onDragEndCallback) {
      this.documentCleanup = [];
      this.currentConfig = { anchorX: "right", x: 20, anchorY: "bottom", y: 40 };
      this.isDragging = false;
      this.hasMoved = false;
      this.startX = 0;
      this.startY = 0;
      this.startLeft = 0;
      this.startTop = 0;
      this.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();
      };
      this.onDragMove = (e) => {
        if (!this.isDragging) return;
        if (e.cancelable) e.preventDefault();
        requestAnimationFrame(() => {
          if (!this.isDragging) return;
          const pos = this.getClientPos(e);
          const dx = pos.x - this.startX;
          const dy = pos.y - this.startY;
          if (Math.abs(dx) > 10 || Math.abs(dy) > 10) this.hasMoved = true;
          const newLeft = Math.min(window.innerWidth - this.container.offsetWidth, Math.max(0, this.startLeft + dx));
          const newTop = Math.min(window.innerHeight - this.container.offsetHeight, Math.max(0, this.startTop + dy));
          this.container.style.left = `${newLeft}px`;
          this.container.style.top = `${newTop}px`;
        });
      };
      this.onDragEnd = () => {
        if (!this.isDragging) return;
        this.isDragging = false;
        if (this.hasMoved) {
          const rect = this.container.getBoundingClientRect();
          const isRight = rect.left + rect.width / 2 > window.innerWidth / 2;
          const isBottom = rect.top + rect.height / 2 > window.innerHeight / 2;
          const targetMargin = 20;
          const targetLeftPx = isRight ? window.innerWidth - rect.width - targetMargin : targetMargin;
          const targetTopPx = Math.min(
            window.innerHeight - rect.height - targetMargin,
            Math.max(targetMargin, rect.top)
          );
          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));
          }
          setTimeout(() => this.applyAnchor(config), 300);
          if (this.onDragEndCallback) {
            this.onDragEndCallback();
          }
        } else {
          this.applyAnchor(this.currentConfig);
        }
      };
      this.container = container2;
      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 });
      const docListeners = [
        ["mousemove", this.onDragMove, { passive: false }],
        ["touchmove", this.onDragMove, { passive: false }],
        ["mouseup", this.onDragEnd],
        ["touchend", this.onDragEnd]
      ];
      for (const [event, handler, options] of docListeners) {
        document.addEventListener(event, handler, options);
        this.documentCleanup.push(() => document.removeEventListener(event, handler, options));
      }
      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 };
    }
    destroy() {
      this.documentCleanup.forEach((fn) => fn());
      this.documentCleanup = [];
      this.trigger.removeEventListener("mousedown", this.onDragStart);
      this.trigger.removeEventListener("touchstart", this.onDragStart);
    }
  }
  const log$3 = Logger.scope("QuickBar");
  class QuickBarService {
    constructor() {
      this.container = null;
      this.documentCleanup = [];
    }
    async onInit() {
      log$3.debug("Initializing FAB interface");
      this.render();
      this.bindGlobalEvents();
    }
    bindGlobalEvents() {
      CoreEvents.on(AppEvents.LANGUAGE_CHANGED, () => {
        MenuService.register();
        this.render();
      });
      CoreEvents.on(AppEvents.PANEL_OPENED, () => {
        if (this.container) this.container.style.display = "none";
      });
      CoreEvents.on(AppEvents.PANEL_CLOSED, () => {
        if (this.container) this.container.style.display = "";
      });
    }
    render() {
      this.documentCleanup.forEach((fn) => fn());
      this.documentCleanup = [];
      if (this.container) this.container.remove();
      if (!State.proxy.enableQuickBar) return;
      const appState = State.proxy;
      this.container = h("div", { className: "fc2-fab-container" });
      const actions = h("div", { className: "fc2-fab-actions" });
      const mkBtn = (iconSvg, title, prop, onClick) => {
        const iconContainer = UIUtils.icon(iconSvg);
        const b = h(
          "button",
          {
            className: `fc2-fab-btn ${prop && appState[prop] ? "active" : ""}`,
            "data-title": title,
            "aria-label": title,
            ...prop ? { "data-prop": prop } : {},
            onclick: (e) => {
              e.preventDefault();
              e.stopPropagation();
              if (prop) {
                appState[prop] = !appState[prop];
              } else if (onClick) onClick();
            }
          },
          iconContainer
        );
        return b;
      };
      actions.appendChild(
        mkBtn(IconRotate, t("labelSyncing"), null, () => {
          SyncService.onProgress = (progress) => Toast.show(`${progress.phase} (${progress.percent}%)`, "info");
          SyncService.performSync(true).catch(() => {
          });
        })
      );
      actions.appendChild(
        mkBtn(IconStar, t("tabCollection"), null, () => {
          SettingsPanel.show("collection");
        })
      );
      actions.appendChild(mkBtn(IconEyeSlash, t("optionHideViewed"), "hideViewed"));
      actions.appendChild(mkBtn(IconMagnet, t("optionHideNoMagnet"), "hideNoMagnet"));
      actions.appendChild(mkBtn(IconBan, t("optionHideCensored"), "hideCensored"));
      actions.appendChild(mkBtn(IconGear, t("tabSettings"), null, SettingsPanel.show));
      actions.appendChild(
        mkBtn(IconArrowUp, t("btnBackToTop"), null, () => window.scrollTo({ top: 0, behavior: "smooth" }))
      );
      const trigger = h(
        "button",
        { className: "fc2-fab-trigger", "aria-label": t("btnMoreOptions") },
        UIUtils.icon(IconPlus),
        h("div", {
          className: `fc2-sync-dot ${appState.syncStatus}`,
          style: { display: appState.syncMode === "none" || appState.syncStatus === "idle" ? "none" : "block" }
        })
      );
      State.on(({ prop, value: val }) => {
        const dot = trigger.querySelector(".fc2-sync-dot");
        if (dot) {
          if (prop === "syncStatus") {
            dot.className = `fc2-sync-dot ${val}`;
            dot.style.display = State.proxy.syncMode === "none" || val === "idle" ? "none" : "block";
          } else if (prop === "syncMode") {
            dot.style.display = val === "none" || State.proxy.syncStatus === "idle" ? "none" : "block";
          }
        }
        if (["hideViewed", "hideNoMagnet", "hideCensored"].includes(prop)) {
          const btn = actions.querySelector(`[data-prop="${prop}"]`);
          if (btn) {
            btn.classList.toggle("active", !!val);
          }
        }
      });
      this.container.append(actions, trigger);
      const shadowWrapper = h("div", { className: "fc2-quickbar-host" }, this.container);
      UIHost.add(shadowWrapper);
      this.initDraggable(this.container, trigger, actions);
    }
    initDraggable(container2, trigger, actions) {
      const dragManager = new DragManager(container2, trigger);
      trigger.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        if (!dragManager.hasMoved) {
          const vis = actions.classList.toggle("visible");
          trigger.classList.toggle("active", vis);
        }
      });
      const closeFAB = (e) => {
        if (!e.composedPath().includes(container2) && 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();
      });
    }
  }
  AppContainer.register("quickbar", new QuickBarService());
  const log$2 = Logger.scope("Main");
  const isCloudflareChallenge = () => {
    return document.title.includes("Just a moment...") || !!document.querySelector("#challenge-running") || !!document.querySelector('iframe[src*="challenges.cloudflare.com"]');
  };
  const main = async () => {
    Logger.init();
    if (isCloudflareChallenge()) {
      log$2.warn("Cloudflare challenge detected, skipping initialization");
      return;
    }
    try {
      Logger.group("Main", `${SCRIPT_INFO.NAME} bootstrap`);
      await AppContainer.bootstrap();
      CoreEvents.emit(AppEvents.UI_READY, {});
      log$2.info("System initialized");
      Logger.groupEnd();
    } catch (error) {
      log$2.error("Bootstrap failed", error);
    }
  };
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", main);
  } else {
    main();
  }
  const Checkbox = (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 container2 = 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 container2;
  };
  const FormRow = (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
    );
  };
  const CheckboxRow = (props) => {
    const { className = "" } = props;
    const checkboxLabel = Checkbox(props);
    checkboxLabel.className = `${checkboxLabel.className} fc2-enh-form-row checkbox ${className}`;
    return checkboxLabel;
  };
  const resolveIcon$1 = (icon) => typeof icon === "string" ? UIUtils.icon(icon) : icon;
  const Button = (props) => {
    const { text, onClick, className = "", icon, title, disabled, style } = props;
    const btn = h(
      "button",
      {
        className: `fc2-enh-btn ${className}`.trim(),
        title: title || "",
        disabled: !!disabled,
        onclick: (e) => {
          const result = onClick(e);
          if (result instanceof Promise) {
            result.catch((err) => Logger.error("UI", "Button action error", err));
          }
        }
      },
      icon ? resolveIcon$1(icon) : null,
      text ? h("span", { className: "fc2-btn-text" }, text) : null
    );
    if (style) {
      Object.assign(btn.style, style);
    }
    return btn;
  };
  const Input = (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;
  };
  const Select = (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;
  };
  const resolveIcon = (icon) => typeof icon === "string" ? UIUtils.icon(icon) : icon;
  const Card = (props) => {
    const { title, subtitle, icon, children, className = "" } = props;
    const childArray = Array.isArray(children) ? children : [children];
    return h(
      "div",
      { className: `fc2-dashboard-card ${className}`.trim() },
      h(
        "h4",
        {},
        icon ? resolveIcon(icon) : null,
        h("span", { className: "fc2-card-title" }, title),
        subtitle ? h("div", { className: "fc2-card-subtitle" }, subtitle) : null
      ),
      ...childArray
    );
  };
  const LEDIndicator = (props) => {
    const { id, active = false, label = "", color } = props;
    const led = h("span", {
      id: id ? `led-${id}` : void 0,
      className: `fc2-led-dot ${active ? "active" : ""}`.trim(),
      ...color ? { style: { background: color } } : {}
    });
    return h(
      "div",
      { className: "fc2-led-group" },
      led,
      h("span", { className: "fc2-led-label", id: id ? `led-label-${id}` : void 0 }, label)
    );
  };
  const StatItem = (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" }, valContainer, h("div", { className: "fc2-stat-label" }, label));
  };
  const renderToggleIcon = (isPassword) => UIUtils.icon(isPassword ? IconEye : IconEyeSlash);
  const PasswordInput = (props) => {
    const input = Input({ ...props, type: "password" });
    const toggle = h("button", {
      className: "fc2-input-toggle",
      type: "button",
      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);
  };
  const log$1 = Logger.scope("Reactive");
  class ReactiveController {
    constructor() {
      this.bindings = new Map();
      this.cleanups = [];
      this.disposed = false;
      const unbind = State.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$1.warn("Attempted to bind on a disposed controller");
        return;
      }
      this.bindings.set(el, options);
      const currentValue = State.proxy[options.key];
      this.updateElement(el, options, currentValue);
      const inputEl = el;
      const isTextControl = el.tagName === "TEXTAREA" || el.tagName === "INPUT" && ["text", "password", "url", "email", "number", "range"].includes(el.type);
      const eventType = isTextControl ? "input" : "change";
      const listener = () => {
        if (this.disposed) return;
        let newValue;
        if (options.mode === "checked") {
          newValue = el.checked;
        } else if (options.mode === "toggle") {
          newValue = !State.proxy[options.key];
        } else {
          const raw = inputEl.value;
          newValue = typeof State.proxy[options.key] === "number" ? Number(raw) : raw;
        }
        State.proxy[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":
          el.textContent = String(value ?? "");
          break;
        default:
          inputEl.value = String(value ?? "");
      }
    }
bindList(container2, options) {
      if (this.disposed) return;
      const render = () => {
        const list = State.proxy[options.key] || [];
        container2.textContent = "";
        list.forEach((item, index) => {
          container2.appendChild(options.renderItem(item, index));
        });
      };
      const unbind = State.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 = State.on(({ prop, value }) => {
        if (this.disposed) return;
        if (prop === key) callback(value);
      });
      this.cleanups.push(unbind);
      callback(State.proxy[key]);
    }
dispose() {
      if (this.disposed) return;
      this.disposed = true;
      this.cleanups.forEach((fn) => fn());
      this.cleanups = [];
      this.bindings.clear();
    }
static create() {
      return new ReactiveController();
    }
  }
  class TabLifecycle {
    constructor() {
      this.controller = null;
      this.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) => fn());
      this.cleanups = [];
    }
  }
  const lifecycle$2 = new TabLifecycle();
  const renderDashboardTab = (_shadow, switchTab) => {
    lifecycle$2.reset();
    const mkIcon2 = (svg) => UIUtils.icon(svg);
    const stats = {
      itemCount: h("span", { textContent: "..." }),
      cacheSize: h("span", { textContent: "..." }),
      syncDetails: h("div", { className: "fc2-stat-label fc2-mt-sm" })
    };
    const updateStats = async () => {
      const items = await CollectionService.getCollectionItems();
      stats.itemCount.textContent = String(items.length);
      const cache = await Repository.cache.getAll();
      stats.cacheSize.textContent = String(cache.length);
    };
    const syncLed = LEDIndicator({ id: "dashboard-sync", active: false, label: "" });
    const syncLabel = syncLed.querySelector(".fc2-led-label");
    const ledDot = syncLed.querySelector(".fc2-led-dot");
    const updateSyncUI = () => {
      const mode = State.proxy.syncMode;
      if (mode === "none") {
        if (syncLabel) syncLabel.textContent = t("dashSyncModeNone");
        if (ledDot) {
          ledDot.classList.remove("active");
          ledDot.style.background = "#666";
        }
        stats.syncDetails.textContent = t("dashSyncSuggestWebDAV");
      } else {
        if (syncLabel) syncLabel.textContent = mode.toUpperCase();
        if (ledDot) {
          ledDot.classList.add("active");
          ledDot.style.background = "";
        }
        stats.syncDetails.textContent = `${t("syncStatus")}: ${mode.toUpperCase()} | NODE_OK`;
      }
    };
    lifecycle$2.addCleanup(
      CoreEvents.on(AppEvents.STATE_CHANGED, ({ prop }) => {
        if (prop === "syncMode") updateSyncUI();
      })
    );
    lifecycle$2.addCleanup(CoreEvents.on(AppEvents.COLLECTION_UPDATED, updateStats));
    const container2 = h(
      "div",
      { className: "fc2-dashboard-container" },
      h(
        "div",
        { className: "fc2-dashboard-grid" },
Card({
          title: t("dashCollStatus"),
          icon: mkIcon2(IconStar),
          children: [
            StatItem({ label: t("dashCollCountLabel"), value: stats.itemCount }),
            h(
              "div",
              { className: "fc2-card-actions" },
              Button({
                text: t("dashEnterColl"),
                className: "primary",
                style: { width: "100%" },
                onClick: () => switchTab("collection")
              })
            )
          ]
        }),
Card({
          title: t("dashSyncTitle"),
          icon: mkIcon2(IconDatabase),
          children: [
            syncLed,
            stats.syncDetails,
            h(
              "div",
              { className: "fc2-card-actions" },
              Button({
                text: t("dashSyncConsole"),
                style: { width: "100%" },
                onClick: () => switchTab("data")
              })
            )
          ]
        }),
Card({
          title: t("dashHealthTitle"),
          icon: mkIcon2(IconBolt),
          children: [
            StatItem({ label: t("dashCacheSizeLabel"), value: stats.cacheSize }),
            h(
              "div",
              { className: "fc2-card-actions fc2-gap-sm" },
              Button({
                text: t("dashRunRepair"),
                className: "danger",
                style: { flex: "1" },
                onClick: async () => {
                  if (confirm(t("dashRepairConfirm"))) {
                    await CollectionService.runHealthCheck(true);
                    updateStats();
                  }
                }
              }),
              Button({
                text: t("dashViewReport"),
                style: { flex: "1" },
                onClick: () => switchTab("debug")
              })
            )
          ]
        })
      )
    );
    updateSyncUI();
    updateStats();
    return container2;
  };
  const onUnmount$4 = () => {
    lifecycle$2.dispose();
  };
  const DashboardTab = Object.freeze( Object.defineProperty({
    __proto__: null,
    onUnmount: onUnmount$4,
    renderDashboardTab
  }, Symbol.toStringTag, { value: "Module" }));
  class SearchIndex {
    constructor() {
      this.index = new Map();
      this.items = new Map();
    }
build(items, fields) {
      this.index.clear();
      this.items.clear();
      items.forEach((item) => {
        const id = item.id || item.code || item.work_id;
        if (!id) return;
        this.items.set(id, item);
        const keywords = new Set();
        fields.forEach((field) => {
          const value = item[field];
          if (value) {
            this.tokenize(String(value)).forEach((token) => keywords.add(token));
          }
        });
        keywords.forEach((keyword) => {
          if (!this.index.has(keyword)) {
            this.index.set(keyword, new Set());
          }
          this.index.get(keyword).add(id);
        });
      });
    }
search(query) {
      if (!query || !query.trim()) return Array.from(this.items.values());
      const tokens2 = this.tokenize(query);
      if (tokens2.length === 0) return Array.from(this.items.values());
      let resultIds = null;
      for (const token of tokens2) {
        const matches = new Set();
        for (const [keyword, ids] of this.index.entries()) {
          if (keyword.includes(token)) {
            ids.forEach((id) => matches.add(id));
          }
        }
        if (resultIds === null) {
          resultIds = matches;
        } else {
          const nextResult = new Set();
          matches.forEach((id) => {
            if (resultIds.has(id)) nextResult.add(id);
          });
          resultIds = nextResult;
        }
        if (resultIds.size === 0) break;
      }
      return resultIds ? Array.from(resultIds).map((id) => this.items.get(id)) : [];
    }
    tokenize(text) {
      return text.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").split(/[\s\-_./\\()[\]]+/).filter((t2) => t2.length > 0);
    }
  }
  const log = Logger.scope("CollectionTab");
  let activeObserver = null;
  let currentUnbinds = [];
  const clearChildren = (el) => {
    el.textContent = "";
  };
  const buildNoResults = () => h(
    "div",
    { className: "fc2-no-results" },
    h("div", { className: "icon" }, UIUtils.icon(IconCircleInfo)),
    h("div", { className: "text" }, t("alertNoPreview"))
  );
  const formatDate = (ts) => {
    if (!ts) return "—";
    return new Date(ts).toLocaleDateString();
  };
  const renderCollectionTab = (shadow) => {
    onUnmount$3();
    let allItems = [];
    let filteredItems = [];
    let collectionStats = null;
    const duplicateKeys = new Set();
    const searchIndex = new SearchIndex();
    const collState = {
      searchQuery: "",
      sort: "date-desc",
      site: "all",
      folder: "all",
      isBatchMode: false,
      selectedIds: new Set(),
      activeTags: new Set()
    };
    const container2 = h("div", { className: "fc2-collection-container" });
    const gallery = h("div", { className: "fc2-collection-grid" });
    const statsDisplay = h("span", { className: "fc2-label-dim fc2-ml-sm" });
    const healthProgress = h("div", { className: "fc2-health-progress is-hidden" });
    const statsHeader = h("div", { className: "fc2-collection-stats-header is-hidden" });
    const renderStatsHeader = (stats) => {
      clearChildren(statsHeader);
      statsHeader.classList.remove("is-hidden");
      const statItems = [
        { label: t("collTotal"), value: String(stats.totalItems), icon: IconStar },
        {
          label: t("collStatsRated"),
          value: stats.ratedCount > 0 ? `${stats.ratedCount} (${t("collStatsAvg")}: ${stats.averageRating.toFixed(1)}★)` : "0",
          icon: IconStar
        },
        { label: t("collStatsWithNotes"), value: String(stats.withNotes), icon: IconCircleInfo },
        { label: t("collStatsWithTags"), value: String(stats.withTags), icon: IconCircleInfo }
      ];
      const grid = h("div", { className: "fc2-stats-grid" });
      statItems.forEach(({ label, value }) => {
        grid.appendChild(
          h(
            "div",
            { className: "fc2-stat-chip" },
            h("span", { className: "label" }, label),
            h("span", { className: "value" }, value)
          )
        );
      });
      statsHeader.appendChild(grid);
      if (stats.oldestItem && stats.newestItem) {
        statsHeader.appendChild(
          h(
            "div",
            { className: "fc2-stats-date-range" },
            `${formatDate(stats.oldestItem)} — ${formatDate(stats.newestItem)}`
          )
        );
      }
    };
    const tagCloud = h("div", { className: "fc2-tag-cloud is-hidden" });
    const renderTagCloud = async () => {
      const tags = await CollectionService.getAllTags();
      clearChildren(tagCloud);
      if (tags.length === 0) {
        tagCloud.classList.add("is-hidden");
        return;
      }
      tagCloud.classList.remove("is-hidden");
      const label = h("span", { className: "fc2-tag-cloud-label" }, `${t("labelUserTags")}:`);
      tagCloud.appendChild(label);
      const maxCount = tags.reduce((max, t2) => Math.max(max, t2.count), 0);
      tags.slice(0, 20).forEach(({ tag, count }) => {
        const isActive = collState.activeTags.has(tag);
        const sizeClass = count >= maxCount * 0.7 ? "large" : count >= maxCount * 0.3 ? "medium" : "small";
        const chip = h(
          "button",
          {
            className: `fc2-tag-cloud-item ${sizeClass} ${isActive ? "active" : ""}`,
            onclick: () => {
              if (collState.activeTags.has(tag)) {
                collState.activeTags.delete(tag);
              } else {
                collState.activeTags.add(tag);
              }
              chip.classList.toggle("active", collState.activeTags.has(tag));
              applyFilters();
            }
          },
          `${tag}`,
          h("span", { className: "fc2-tag-count" }, `${count}`)
        );
        tagCloud.appendChild(chip);
      });
      if (collState.activeTags.size > 0) {
        tagCloud.appendChild(
          h(
            "button",
            {
              className: "fc2-tag-cloud-clear",
              onclick: () => {
                collState.activeTags.clear();
                renderTagCloud();
                applyFilters();
              }
            },
            `✕ ${t("btnDeselectAll")}`
          )
        );
      }
    };
    const backToTop = h(
      "button",
      {
        className: "fc2-back-to-top is-hidden",
        onclick: () => {
          const body = shadow.querySelector(".fc2-enh-settings-content");
          if (body) body.scrollTo({ top: 0, behavior: "smooth" });
        }
      },
      UIUtils.icon(IconChevronUp)
    );
    const applyFilters = () => {
      const results = searchIndex.search(collState.searchQuery);
      filteredItems = results.filter((item) => {
        const matchesSite = collState.site === "all" || (item.type || "FC2").toLowerCase() === collState.site;
        const matchesFolder = collState.folder === "all" || (item.folder || "wanted") === collState.folder;
        if (collState.sort === "rating" && !item.rating) return false;
        if (collState.sort === "notes" && !item.notes) return false;
        if (collState.activeTags.size > 0) {
          const itemTags = new Set(item.userTags || []);
          const hasMatchingTag = Array.from(collState.activeTags).some((t2) => itemTags.has(t2));
          if (!hasMatchingTag) return false;
        }
        return matchesSite && matchesFolder;
      });
      filteredItems.sort((a, b) => {
        switch (collState.sort) {
          case "date-desc":
            return (b.lastAccessed || 0) - (a.lastAccessed || 0);
          case "date-asc":
            return (a.lastAccessed || 0) - (b.lastAccessed || 0);
          case "title":
            return (a.title || "").localeCompare(b.title || "");
          case "site":
            return (a.type || "").localeCompare(b.type || "");
          case "rating":
            return (b.rating || 0) - (a.rating || 0);
          case "folder":
            return (a.folder || "wanted").localeCompare(b.folder || "wanted");
          default:
            return 0;
        }
      });
      statsDisplay.textContent = `${t("collTotal")}: ${allItems.length} | ${t("collShown")}: ${filteredItems.length}`;
      renderGallery();
    };
    const debouncedFilter = Utils.debounce(applyFilters, 200);
    let itemsToShow = 30;
    const CHUNK_SIZE = 30;
    const renderGallery = (append = false) => {
      if (!append) {
        clearChildren(gallery);
        itemsToShow = CHUNK_SIZE;
        if (activeObserver) activeObserver.disconnect();
      }
      const itemsToRender = filteredItems.slice(append ? itemsToShow - CHUNK_SIZE : 0, itemsToShow);
      if (filteredItems.length === 0) {
        gallery.appendChild(buildNoResults());
        return;
      }
      itemsToRender.forEach((item) => {
        const { finalElement } = EnhancedCard(item, () => {
        }, { skipFilters: true, minimal: false });
        const card2 = finalElement;
        card2.classList.add(Config.CLASSES.cardRebuilt);
        card2.removeAttribute("data-enh-searching");
        card2.classList.toggle("is-selected", collState.selectedIds.has(item.id));
        card2.onclick = (e) => {
          if (collState.isBatchMode) {
            e.preventDefault();
            if (collState.selectedIds.has(item.id)) collState.selectedIds.delete(item.id);
            else collState.selectedIds.add(item.id);
            card2.classList.toggle("is-selected", collState.selectedIds.has(item.id));
            updateBatchBar();
          }
        };
        const removeIcon = h(
          "div",
          {
            className: `fc2-card-remove-overlay ${collState.isBatchMode ? "is-hidden" : ""}`.trim(),
            onclick: async (e) => {
              e.stopPropagation();
              if (!confirm(t("confirmDelete"))) return;
              const prev = { ...item };
              await CollectionService.remove(item.id);
              Toast.show(t("collRemoveSuccess"), "info", {
                action: { label: t("collUndo"), onClick: () => CollectionService.add(prev.id, prev) }
              });
            }
          },
          "×"
        );
        if (item.folder && item.folder !== "wanted") {
          const folderBadge = h("div", { className: "fc2-card-folder-badge" }, item.folder);
          card2.appendChild(folderBadge);
        }
        if (item.rating && item.rating > 0) {
          const stars = h("div", { className: "fc2-card-rating-indicator" }, "★".repeat(item.rating));
          card2.appendChild(stars);
        }
        card2.appendChild(removeIcon);
        gallery.appendChild(card2);
      });
      if (itemsToShow < filteredItems.length) {
        const sentinel = h("div", { className: "fc2-gallery-sentinel" });
        gallery.appendChild(sentinel);
        activeObserver = new IntersectionObserver(
          (entries) => {
            const entry = entries[0];
            if (entry && entry.isIntersecting) {
              activeObserver?.disconnect();
              requestAnimationFrame(() => {
                itemsToShow += CHUNK_SIZE;
                renderGallery(true);
              });
            }
          },
          { rootMargin: "400px" }
        );
        activeObserver.observe(sentinel);
      }
    };
    const batchBar = h("div", { className: "fc2-batch-action-bar is-hidden" });
    let availableFolders = [];
    const updateBatchBar = () => {
      if (collState.isBatchMode) {
        batchBar.classList.remove("is-hidden");
      } else {
        batchBar.classList.add("is-hidden");
      }
      clearChildren(batchBar);
      if (!collState.isBatchMode) return;
      const count = collState.selectedIds.size;
      batchBar.append(
        h(
          "div",
          { className: "batch-info" },
          h("span", { className: "count" }, t("collSelected", { count })),
          Button({
            text: count === filteredItems.length ? t("btnDeselectAll") : t("btnSelectAll"),
            className: "ghost micro",
            onClick: () => {
              if (count === filteredItems.length) {
                collState.selectedIds.clear();
              } else {
                filteredItems.forEach((item) => collState.selectedIds.add(item.id));
              }
              updateBatchBar();
              renderGallery();
            }
          })
        ),
        h(
          "div",
          { className: "batch-actions" },
          Button({
            text: t("collBatchMove"),
            className: "secondary",
            disabled: count === 0,
            onClick: async () => {
              const target = prompt(t("collMoveTarget", { folders: availableFolders.join(", ") }));
              if (target) {
                const result = await CollectionService.batchMoveToFolder(
                  Array.from(collState.selectedIds),
                  target
                );
                collState.selectedIds.clear();
                collState.isBatchMode = false;
                loadData();
                if (result.success) {
                  Toast.show(t("collMoveSuccess", { count, target }), "success");
                }
              }
            }
          }),
          Button({
            text: t("collBatchMerge"),
            className: "secondary",
            disabled: count < 2,
            onClick: async () => {
              if (confirm(t("collBatchMerge") + "?")) {
                const ids = Array.from(collState.selectedIds);
                const master = ids[0];
                if (!master) return;
                const dupes = ids.slice(1);
                const result = await CollectionService.mergeItems(master, dupes);
                collState.selectedIds.clear();
                collState.isBatchMode = false;
                loadData();
                if (result.success) {
                  Toast.show(t("alertMetadataSaved"), "success");
                }
              }
            }
          }),
          Button({
            text: t("collBatchRemove"),
            className: "danger",
            disabled: count === 0,
            onClick: async () => {
              if (confirm(t("confirmDeleteSelected", { count }))) {
                const result = await CollectionService.batchRemove(Array.from(collState.selectedIds));
                collState.selectedIds.clear();
                collState.isBatchMode = false;
                updateBatchBar();
                loadData();
                if (result.success) {
                  Toast.show(t("collBatchRemoveSuccess"), "success");
                }
              }
            }
          }),
          Button({
            text: t("btnCancel"),
            onClick: () => {
              collState.isBatchMode = false;
              collState.selectedIds.clear();
              updateBatchBar();
              renderGallery();
            }
          })
        )
      );
    };
    const searchInput = h("input", {
      className: "fc2-enh-input",
      placeholder: t("searchPlaceholder"),
      value: collState.searchQuery,
      oninput: (e) => {
        collState.searchQuery = e.target.value;
        if (collState.searchQuery) {
          clearBtn.classList.remove("is-invisible");
        } else {
          clearBtn.classList.add("is-invisible");
        }
        debouncedFilter();
      }
    });
    const clearBtn = h(
      "button",
      {
        className: "fc2-search-clear is-invisible",
        onclick: () => {
          collState.searchQuery = "";
          searchInput.value = "";
          clearBtn.classList.add("is-invisible");
          applyFilters();
        }
      },
      "×"
    );
    const folderSelectContainer = h("div", { className: "fc2-select-container" });
    const renderFolderSelect = () => {
      clearChildren(folderSelectContainer);
      folderSelectContainer.appendChild(
        Select({
          id: "coll-folder",
          value: collState.folder,
          options: [
            { label: t("collFolderAll"), value: "all" },
            ...availableFolders.map((f) => ({
              label: f === "wanted" ? t("folderWanted") : f === "viewed" ? t("folderViewed") : f === "follow" ? t("folderFollow") : f,
              value: f
            }))
          ],
          onChange: (v) => {
            collState.folder = v;
            applyFilters();
          }
        })
      );
    };
    const handleExport = async () => {
      try {
        const exportData = await CollectionService.exportCollection();
        const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: "application/json" });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = `fc2ppvdb-collection-${( new Date()).toISOString().slice(0, 10)}.json`;
        a.click();
        URL.revokeObjectURL(url);
        Toast.show(t("alertExportSuccess"), "success");
      } catch (err) {
        log.error("Export failed", err);
        Toast.show(t("labelError"), "error");
      }
    };
    const handleImport = () => {
      const input = document.createElement("input");
      input.type = "file";
      input.accept = ".json";
      input.onchange = async () => {
        const file = input.files?.[0];
        if (!file) return;
        try {
          const text = await file.text();
          const data = JSON.parse(text);
          const result = await CollectionService.importCollection(data);
          if (result.success) {
            Toast.show(t("collImportSuccess", { count: result.affected }), "success");
          } else {
            Toast.show(
              `${t("collImportPartial", { count: result.affected, errors: result.errors.length })}`,
              "warn"
            );
          }
          loadData();
        } catch {
          Toast.show(t("alertImportError"), "error");
        }
      };
      input.click();
    };
    const toolbar = h(
      "div",
      { className: "fc2-collection-toolbar" },
      h(
        "div",
        { className: "toolbar-group search" },
        UIUtils.icon(IconMagnifyingGlass),
        h("div", { className: "input-wrapper" }, searchInput, clearBtn),
        statsDisplay
      ),
      h(
        "div",
        { className: "toolbar-group filters" },
        Select({
          id: "coll-site",
          value: collState.site,
          options: [
            { label: t("collSiteAll"), value: "all" },
            ...PortalService.getAllSites().map((s) => ({
              label: s.toUpperCase(),
              value: s.toLowerCase()
            }))
          ],
          onChange: (v) => {
            collState.site = v;
            applyFilters();
          }
        }),
        folderSelectContainer
      ),
      h(
        "div",
        { className: "toolbar-group actions" },
        healthProgress,
        Select({
          id: "coll-sort",
          value: collState.sort,
          options: [
            { label: t("collSortNewest"), value: "date-desc" },
            { label: t("collSortOldest"), value: "date-asc" },
            { label: t("collSortTitle"), value: "title" },
            { label: t("collSortFolder"), value: "folder" },
            { label: t("labelRating"), value: "rating" }
          ],
          onChange: (v) => {
            collState.sort = v;
            applyFilters();
          }
        }),
        Button({
          text: t("collBatchMode"),
          className: collState.isBatchMode ? "active" : "",
          onClick: () => {
            collState.isBatchMode = !collState.isBatchMode;
            updateBatchBar();
            renderGallery();
          }
        }),
        Button({
          text: "",
          icon: UIUtils.icon(IconClockRotateLeft),
          className: "icon-only",
          title: t("dashRunRepair"),
          onClick: async () => {
            const result = await CollectionService.runHealthCheck(true);
            Toast.show(
              t("collHealthResult", { checked: result.checked, repaired: result.repaired }),
              result.repaired > 0 ? "success" : "info"
            );
            loadData();
          }
        }),
        Button({
          text: "",
          icon: UIUtils.icon(IconDatabase),
          className: "icon-only",
          title: t("collExportJson"),
          onClick: handleExport
        }),
        Button({
          text: "",
          icon: UIUtils.icon(IconRotate),
          className: "icon-only",
          title: t("collImportJson"),
          onClick: handleImport
        })
      )
    );
    const folderBar = h("div", { className: "fc2-folder-management-bar" });
    const renderFolderBar = () => {
      clearChildren(folderBar);
      const currentFolder = collState.folder;
      const isSystemFolder = currentFolder === "all" || SYSTEM_FOLDERS.includes(currentFolder);
      const actions = h("div", { className: "fc2-folder-actions" });
      actions.appendChild(
        Button({
          text: t("collNewFolder"),
          className: "ghost micro",
          onClick: () => {
            const name = prompt(t("promptFolderName"));
            if (name && name.trim()) {
              if (!availableFolders.includes(name.trim())) {
                availableFolders.push(name.trim());
                renderFolderSelect();
              }
              collState.folder = name.trim();
              applyFilters();
            }
          }
        })
      );
      if (!isSystemFolder && currentFolder !== "all") {
        actions.appendChild(
          Button({
            text: t("collRenameFolder"),
            className: "ghost micro",
            onClick: async () => {
              const newName = prompt(t("promptNewFolderName"));
              if (newName && newName.trim()) {
                const result = await CollectionService.renameFolder(currentFolder, newName.trim());
                if (result.success) {
                  Toast.show(t("alertFolderRenamed"), "success");
                  collState.folder = newName.trim();
                  loadData();
                }
              }
            }
          })
        );
        actions.appendChild(
          Button({
            text: t("collDeleteFolder"),
            className: "ghost micro danger",
            onClick: async () => {
              if (confirm(t("confirmDeleteFolder", { folder: currentFolder }))) {
                const result = await CollectionService.deleteFolder(currentFolder);
                if (result.success) {
                  collState.folder = "all";
                  loadData();
                }
              }
            }
          })
        );
      }
      if (CollectionService.canUndo()) {
        const undoLabel = CollectionService.getUndoLabel();
        actions.appendChild(
          Button({
            text: `${t("collUndo")}${undoLabel ? `: ${undoLabel}` : ""}`,
            className: "ghost micro",
            onClick: async () => {
              const ok = await CollectionService.undo();
              if (ok) {
                Toast.show(t("collUndo"), "success");
                loadData();
              }
            }
          })
        );
      }
      folderBar.appendChild(actions);
    };
    const loadData = async (e) => {
      allItems = await CollectionService.getCollectionItems();
      availableFolders = await CollectionService.getFolders();
      collectionStats = await CollectionService.getStats();
      renderFolderSelect();
      renderFolderBar();
      if (collectionStats) {
        renderStatsHeader(collectionStats);
      }
      renderTagCloud();
      const isStructural = !e || !["metadata", "move"].includes(e.type || "");
      if (isStructural) {
        searchIndex.build(allItems, ["id", "title", "userTags", "notes", "site", "code"]);
        const dupes = await CollectionService.findDuplicates();
        duplicateKeys.clear();
        dupes.forEach((g) => {
          const item = g[0];
          if (!item) return;
          const m = item.id.match(/\d{5,8}/);
          duplicateKeys.add(m && m[0] ? `fc2-${m[0]}` : item.id.toLowerCase());
        });
      }
      applyFilters();
    };
    currentUnbinds.push(CoreEvents.on(AppEvents.COLLECTION_UPDATED, loadData));
    currentUnbinds.push(
      CoreEvents.on(AppEvents.COLLECTION_HEALTH_PROGRESS, (data) => {
        healthProgress.classList.remove("is-hidden");
        clearChildren(healthProgress);
        healthProgress.append(UIUtils.icon(IconBolt), ` Repairing: ${data.processed}/${data.total}`);
        if (data.processed === data.total) setTimeout(() => healthProgress.classList.add("is-hidden"), 3e3);
      })
    );
    const scrollListener = (e) => {
      const target = e.target;
      if (target.scrollTop > 500) {
        backToTop.classList.remove("is-hidden");
      } else {
        backToTop.classList.add("is-hidden");
      }
    };
    setTimeout(() => {
      const content = shadow.querySelector(".fc2-enh-settings-content");
      if (content) {
        content.addEventListener("scroll", scrollListener);
        currentUnbinds.push(() => content.removeEventListener("scroll", scrollListener));
      }
    }, 100);
    container2.append(statsHeader, toolbar, tagCloud, folderBar, batchBar, gallery, backToTop);
    loadData();
    return container2;
  };
  const onUnmount$3 = () => {
    if (activeObserver) {
      activeObserver.disconnect();
      activeObserver = null;
    }
    currentUnbinds.forEach((fn) => fn());
    currentUnbinds = [];
  };
  const CollectionTab = Object.freeze( Object.defineProperty({
    __proto__: null,
    onUnmount: onUnmount$3,
    renderCollectionTab
  }, Symbol.toStringTag, { value: "Module" }));
  const lifecycle$1 = new TabLifecycle();
  const buildStorageSection = (render) => Card({
    title: t("groupDataManagement"),
    icon: IconDatabase,
    children: [
      h(
        "div",
        { className: "fc2-grid-actions" },
        Button({
          text: t("btnClearCache"),
          className: "danger ghost",
          onClick: async () => {
            if (confirm(t("confirmResetDatabase")) && confirm(t("confirmDestructiveAction"))) {
              await Repository.cache.clear();
              Toast.show(t("alertCacheCleared"), "success");
            }
          }
        }),
        Button({
          text: t("btnClearHistory"),
          className: "danger ghost",
          onClick: async () => {
            if (confirm(t("confirmResetDatabase")) && confirm(t("confirmDestructiveAction"))) {
              await HistoryService.clear();
              Toast.show(t("alertHistoryCleared"), "success");
            }
          }
        }),
        Button({
          text: t("btnExportData"),
          icon: UIUtils.icon(IconFileExport),
          onClick: () => {
            void BackupService.exportData();
          }
        }),
        Button({
          text: t("btnImportData"),
          icon: UIUtils.icon(IconFileImport),
          onClick: () => {
            const input = h("input", { type: "file", accept: ".json" });
            input.onchange = async () => {
              if (!input.files?.[0]) return;
              const success = await BackupService.importData(input.files[0]);
              if (success) {
                Toast.show(t("alertImportSuccess"), "success");
                setTimeout(() => location.reload(), TIMING.RELOAD_DELAY_NORMAL);
              } else {
                Toast.show(t("alertImportError"), "error");
              }
            };
            input.click();
          }
        })
      ),
      FormRow({
        label: t("labelDebugMode"),
        children: h(
          "div",
          { className: "fc2-input-group" },
          Button({
            text: State.proxy.debugMode ? t("statusDebugOn") : t("statusDebugOff"),
            className: State.proxy.debugMode ? "primary" : "",
            onClick: () => {
              State.proxy.debugMode = !State.proxy.debugMode;
              Toast.show(State.proxy.debugMode ? t("alertDebugOn") : t("alertDebugOff"), "info");
              render("data");
            }
          }),
          Button({
            text: t("btnCopyEnv"),
            icon: UIUtils.icon(IconSliders),
            onClick: () => {
              const info = {
                version: SCRIPT_INFO.VERSION,
                ua: navigator.userAgent,
                url: location.href,
                syncMode: State.proxy.syncMode,
                debugMode: State.proxy.debugMode,
                storage: { timestamp: ( new Date()).toISOString() }
              };
              navigator.clipboard.writeText(JSON.stringify(info, null, 2));
              Toast.show(t("alertEnvCopied"), "success");
            }
          })
        )
      })
    ]
  });
  const buildSyncConfigSection = (ctrl, render) => {
    const children = [
      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: () => render("data")
        })
      })
    ];
    if (State.proxy.syncMode !== "none") {
      children.push(
        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"
          })
        })
      );
    }
    return Card({
      title: t("syncStatus"),
      icon: IconLink,
      children
    });
  };
  const buildWebDAVSection = (ctrl) => 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" })
      }),
      h(
        "div",
        { className: "fc2-card-actions" },
        Button({
          text: t("btnWebDAVTest"),
          onClick: async () => {
            try {
              await SyncService.testWebDAV();
              Toast.show(t("alertWebDAVSuccess"), "success");
            } catch {
              Toast.show(t("alertWebDAVError"), "error");
            }
          }
        }),
        Button({
          text: t("btnWebDAVSync"),
          className: "primary",
          onClick: () => SyncService.performSync(true)
        }),
        Button({
          text: t("btnForceSync"),
          className: "danger ghost",
          onClick: () => {
            if (confirm(t("confirmForceSync"))) SyncService.forceFullSync();
          }
        })
      )
    ]
  });
  const buildSupabaseSection = (ctrl, render, displayTime) => Card({
    title: t("labelSupabaseSync"),
    icon: IconAdjustments,
    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" })
      }),
      h(
        "div",
        { className: "fc2-auth-section" },
        h("p", { className: "dim" }, `${t("labelLastSync")}: ${displayTime}`),
        h(
          "div",
          { className: "fc2-card-actions" },
          Button({
            text: t("btnConnectAndSync"),
            className: "primary",
            onClick: async () => {
              Logger.debug("DataTab", "Connect and Sync clicked", {
                url: State.proxy.supabaseUrl,
                key: !!State.proxy.supabaseKey,
                email: State.proxy.supabaseEmail,
                hasPass: !!State.proxy.supabasePassword
              });
              try {
                if (!State.proxy.supabaseUrl || !State.proxy.supabaseKey) {
                  throw new Error(t("alertSbUrlRequired") || "Missing Supabase URL or Key");
                }
                if (State.proxy.supabaseEmail && State.proxy.supabasePassword) {
                  Logger.debug("DataTab", "Attempting login...");
                  await SyncService.login(State.proxy.supabaseEmail, State.proxy.supabasePassword);
                  Toast.show(t("alertSyncAccountConnected") || "Account connected", "success");
                  render("data");
                }
                await SyncService.performSync(true);
              } catch (e) {
                Logger.error("DataTab", "Action failed", e);
                let msg = e instanceof Error ? e.message : String(e);
                if (e && typeof e === "object" && "response" in e && typeof e.response === "string") {
                  try {
                    const errBody = JSON.parse(e.response);
                    if (errBody.message)
                      msg = `${errBody.message}${errBody.hint ? ` (${errBody.hint})` : ""}`;
                  } catch {
                  }
                }
                Toast.show(`${t("labelError") || "Error"}: ${msg}`, "error", { duration: 1e4 });
              }
            }
          }),
          Button({ text: t("btnWebDAVSync"), onClick: () => SyncService.performSync(true) }),
          Button({
            text: t("btnForceSync") || "Force Push",
            className: "danger ghost",
            onClick: () => SyncService.forceFullSync()
          }),
          Button({
            text: t("btnPullSync") || "Force Pull",
            className: "danger ghost",
            onClick: () => SyncService.forcePullSync()
          }),
          Button({
            text: t("btnLogout"),
            onClick: async () => {
              await SyncService.logout();
              render("data");
            }
          })
        )
      )
    ]
  });
  const renderDataTab = (_shadow, render) => {
    const ctrl = lifecycle$1.reset();
    const lastSync = typeof GM_getValue !== "undefined" ? GM_getValue(STORAGE_KEYS.LAST_SYNC_TS, t("labelNever")) : t("labelNever");
    const displayTime = lastSync !== t("labelNever") ? new Date(lastSync).toLocaleString() : t("labelNever");
    const sections = [
      buildStorageSection(render),
      buildSyncConfigSection(ctrl, render),
      State.proxy.syncMode === "webdav" ? buildWebDAVSection(ctrl) : null,
      State.proxy.syncMode === "supabase" ? buildSupabaseSection(ctrl, render, displayTime) : null
    ];
    return h("div", { className: "fc2-data-container" }, ...sections.filter((s) => s !== null));
  };
  const onUnmount$2 = () => {
    lifecycle$1.dispose();
  };
  const DataTab = Object.freeze( Object.defineProperty({
    __proto__: null,
    onUnmount: onUnmount$2,
    renderDataTab
  }, Symbol.toStringTag, { value: "Module" }));
  const lifecycle = new TabLifecycle();
  const renderSettingsTab = () => {
    const ctrl = lifecycle.reset();
    const portals = PortalService.getAllPortals();
    const portalGrid = h(
      "div",
      { className: "portal-grid" },
      ...portals.map((p) => {
        const enabled = State.proxy.enabledPortals || [];
        const isEnabled = enabled.includes(p.id);
        return h(
          "label",
          {
            className: `portal-item ${isEnabled ? "active" : ""}`,
            "data-portal-id": p.id
          },
          h("input", {
            type: "checkbox",
            checked: isEnabled,
            onchange: (e) => {
              const checked = e.target.checked;
              let newEnabled = [...State.proxy.enabledPortals];
              if (checked && !newEnabled.includes(p.id)) newEnabled.push(p.id);
              if (!checked) newEnabled = newEnabled.filter((id) => id !== p.id);
              State.proxy.enabledPortals = newEnabled;
              PortalService.clearCache();
            }
          }),
          h("span", {}, p.name)
        );
      })
    );
    ctrl.listen("enabledPortals", (val) => {
      const enabled = val || [];
      portalGrid.querySelectorAll(".portal-item").forEach((el) => {
        const id = el.getAttribute("data-portal-id");
        if (!id) return;
        const active = enabled.includes(id);
        el.classList.toggle("active", active);
        const input = el.querySelector("input");
        if (input) input.checked = active;
      });
    });
    return h(
      "div",
      { className: "fc2-settings-tab" },
      h(
        "div",
        { className: "fc2-settings-grid" },
Card({
          title: t("groupFilters"),
          icon: IconFilter,
          children: [
            CheckboxRow({
              id: "hideNoMagnet",
              label: t("optionHideNoMagnet"),
              controller: ctrl,
              binding: "hideNoMagnet"
            }),
            CheckboxRow({
              id: "hideCensored",
              label: t("optionHideCensored"),
              controller: ctrl,
              binding: "hideCensored"
            }),
            CheckboxRow({
              id: "hideViewed",
              label: t("optionHideViewed"),
              controller: ctrl,
              binding: "hideViewed"
            })
          ]
        }),
Card({
          title: t("groupAppearance"),
          icon: IconPalette,
          children: [
            FormRow({
              label: t("labelPreviewMode"),
              children: Select({
                id: "previewMode",
                options: [
                  { value: "static", label: t("previewModeStatic") },
                  { value: "hover", label: t("previewModeHover") }
                ],
                controller: ctrl,
                binding: "previewMode"
              })
            }),
            FormRow({
              label: t("labelGridColumns"),
              children: Select({
                id: "gridColumns",
                options: [0, 1, 2, 3, 4, 5, 6].map((i) => ({
                  value: String(i),
                  label: i === 0 ? t("labelDefault") : String(i)
                })),
                controller: ctrl,
                binding: "userGridColumns",
                onChange: (value) => {
                  CoreEvents.emit(AppEvents.GRID_CHANGED, Number(value));
                }
              })
            }),
            FormRow({
              label: t("labelLanguage"),
              children: Select({
                id: "language",
                options: [
                  { value: "auto", label: t("langAuto") },
                  { value: "zh", label: t("langZh") },
                  { value: "en", label: t("langEn") }
                ],
                controller: ctrl,
                binding: "language"
              })
            })
          ]
        }),
Card({
          title: t("groupDataHistory"),
          icon: IconClockRotateLeft,
          className: "full-width",
          children: h(
            "div",
            { className: "fc2-settings-card-grid" },
            CheckboxRow({
              id: "enableMagnets",
              label: t("optionEnableMagnets"),
              controller: ctrl,
              binding: "enableMagnets"
            }),
            CheckboxRow({
              id: "enableExternalLinks",
              label: t("optionEnableExternalLinks"),
              controller: ctrl,
              binding: "enableExternalLinks"
            }),
            CheckboxRow({
              id: "enableActressName",
              label: t("optionEnableActressName"),
              controller: ctrl,
              binding: "enableActressName"
            }),
            CheckboxRow({
              id: "replaceFc2Covers",
              label: t("optionReplaceFc2Covers"),
              controller: ctrl,
              binding: "replaceFc2Covers"
            }),
            CheckboxRow({
              id: "enableHistory",
              label: t("optionEnableHistory"),
              controller: ctrl,
              binding: "enableHistory"
            }),
            CheckboxRow({
              id: "loadExtraPreviews",
              label: t("optionLoadExtraPreviews"),
              controller: ctrl,
              binding: "loadExtraPreviews"
            }),
            CheckboxRow({
              id: "enableQuickBar",
              label: t("optionEnableQuickBar"),
              controller: ctrl,
              binding: "enableQuickBar"
            }),
            CheckboxRow({
              id: "showViewedBtn",
              label: t("optionShowViewedBtn"),
              controller: ctrl,
              binding: "showViewedBtn"
            }),
            CheckboxRow({
              id: "showIdBadge",
              label: t("optionShowIdBadge"),
              controller: ctrl,
              binding: "showIdBadge"
            })
          )
        }),
Card({
          title: t("groupExternalPortals"),
          icon: IconLink,
          className: "full-width",
          children: [
            h(
              "div",
              { className: "fc2-portal-actions" },
              Button({
                text: t("btnSelectAll"),
                className: "ghost micro",
                onClick: () => {
                  State.proxy.enabledPortals = portals.map((p) => p.id);
                  PortalService.clearCache();
                }
              }),
              Button({
                text: t("btnDeselectAll"),
                className: "ghost micro",
                onClick: () => {
                  State.proxy.enabledPortals = [];
                  PortalService.clearCache();
                }
              })
            ),
            portalGrid
          ]
        })
      )
    );
  };
  const onUnmount$1 = () => {
    lifecycle.dispose();
  };
  const SettingsTab = Object.freeze( Object.defineProperty({
    __proto__: null,
    onUnmount: onUnmount$1,
    renderSettingsTab
  }, Symbol.toStringTag, { value: "Module" }));
  let container = null;
  let listContainer = null;
  const activeFilters = {
    [LogLevel.ERROR]: true,
    [LogLevel.WARN]: true,
    [LogLevel.INFO]: true,
    [LogLevel.DEBUG]: false,
    [LogLevel.TRACE]: false
  };
  const createLogItem = (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)
    );
    if (entry.data) {
      const dataView = h(
        "pre",
        { className: "fc2-log-payload", style: { display: "none" } },
        JSON.stringify(entry.data, null, 2)
      );
      const toggle = h(
        "button",
        {
          className: "fc2-log-payload-toggle",
          onclick: () => {
            const isHidden = dataView.style.display === "none";
            dataView.style.display = isHidden ? "block" : "none";
          }
        },
        UIUtils.icon(IconDatabase),
        " Payload"
      );
      item.append(toggle, dataView);
    }
    return item;
  };
  const refresh = () => {
    if (!listContainer) return;
    listContainer.textContent = "";
    const logs = [...Logger.history].reverse().filter((entry) => activeFilters[entry.level]);
    logs.forEach((entry) => listContainer.appendChild(createLogItem(entry)));
  };
  const renderDebugTab = () => {
    container = h(
      "div",
      { className: "fc2-debug-container" },
      h(
        "div",
        { className: "fc2-debug-header" },
        h(
          "div",
          { className: "fc2-debug-actions" },
          Button({
            icon: UIUtils.icon(IconMagnifyingGlass),
            text: t("btnCopyAll"),
            onClick: () => {
              const text = Logger.history.map((e) => {
                const count = e.count && e.count > 1 ? ` x${e.count}` : "";
                return `[${e.timestamp}] [${e.levelName}${count}] [${e.module}] ${e.message}`;
              }).join("\n");
              navigator.clipboard.writeText(text);
              Toast.show(t("alertLogsCopied"), "success");
            }
          }),
          Button({
            text: t("btnClearLogs"),
            className: "danger",
            onClick: () => {
              Logger.clear();
              refresh();
            }
          })
        ),
        h(
          "div",
          { className: "fc2-debug-filters" },
          h("span", { className: "fc2-label-dim" }, t("labelLogFilters")),
          ...[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;
  };
  const onUnmount = () => {
    container = null;
    listContainer = null;
  };
  const DebugTab = Object.freeze( Object.defineProperty({
    __proto__: null,
    onUnmount,
    renderDebugTab
  }, Symbol.toStringTag, { value: "Module" }));
  const safeContent = (htmlStr, className) => h("div", { className, innerHTML: htmlStr });
  const 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"),
        subtitle: t("labelDisclaimer"),
        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", className: "fc2-link" }, t("labelGreasyFork")),
        h("span", { className: "dim" }, " | "),
        h("span", { className: "dim" }, "Designed for Efficiency & Privacy")
      )
    );
  };
  const AboutTab = Object.freeze( Object.defineProperty({
    __proto__: null,
    renderAboutTab
  }, Symbol.toStringTag, { value: "Module" }));

})(Dexie);