Greasy Fork

来自缓存

Greasy Fork is available in English.

Discourse 表情管理器 (Emoji Manager) mgr lite

Discourse 论坛表情管理 - 设置、导入导出、分组编辑 (Emoji management for Discourse - Settings, import/export, group editor)

// ==UserScript==
// @name         Discourse 表情管理器 (Emoji Manager) mgr lite
// @namespace    https://github.com/stevessr/bug-v3
// @version      1.2.4
// @description  Discourse 论坛表情管理 - 设置、导入导出、分组编辑 (Emoji management for Discourse - Settings, import/export, group editor)
// @author       stevessr
// @match        https://linux.do/*
// @match        https://meta.discourse.org/*
// @match        https://*.discourse.org/*
// @match        http://localhost:5173/*
// @exclude      https://linux.do/a/*
// @match        https://idcflare.com/*
// @grant        none
// @license      MIT
// @homepageURL  https://github.com/stevessr/bug-v3
// @supportURL   https://github.com/stevessr/bug-v3/issues
// @run-at       document-end
// ==/UserScript==

(function() {
'use strict';

(function() {
	var __defProp = Object.defineProperty;
	var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
	var __export = (all) => {
		let target = {};
		for (var name in all) __defProp(target, name, {
			get: all[name],
			enumerable: true
		});
		return target;
	};
	async function fetchPackagedJSON(url) {
		try {
			if (typeof fetch === "undefined") return null;
			const res = await fetch(url || "/assets/defaultEmojiGroups.json", {
				cache: "no-cache",
				credentials: "omit"
			});
			if (!res.ok) return null;
			return await res.json();
		} catch (err) {
			return null;
		}
	}
	async function loadAndFilterDefaultEmojiGroups(url, hostname) {
		const packaged = await fetchPackagedJSON(url);
		if (!packaged || !Array.isArray(packaged.groups)) return [];
		if (!hostname) return packaged.groups;
		return packaged.groups.map((group) => {
			const filteredEmojis = group.emojis.filter((emoji) => {
				try {
					const url$1 = emoji.url;
					if (!url$1) return false;
					const emojiHostname = new URL(url$1).hostname;
					return emojiHostname === hostname || emojiHostname.endsWith("." + hostname);
				} catch (e) {
					return true;
				}
			});
			return {
				...group,
				emojis: filteredEmojis
			};
		}).filter((group) => group.emojis.length > 0);
	}
	var init_default_emoji_loader = __esmMin((() => {}));
	function loadDataFromLocalStorage() {
		try {
			const groupsData = localStorage.getItem(STORAGE_KEY);
			let emojiGroups = [];
			if (groupsData) try {
				const parsed = JSON.parse(groupsData);
				if (Array.isArray(parsed) && parsed.length > 0) emojiGroups = parsed;
			} catch (e) {
				console.warn("[Userscript] Failed to parse stored emoji groups:", e);
			}
			if (emojiGroups.length === 0) emojiGroups = [];
			const settingsData = localStorage.getItem(SETTINGS_KEY);
			let settings = { ...DEFAULT_USER_SETTINGS };
			if (settingsData) try {
				const parsed = JSON.parse(settingsData);
				if (parsed && typeof parsed === "object") settings = {
					...settings,
					...parsed
				};
			} catch (e) {
				console.warn("[Userscript] Failed to parse stored settings:", e);
			}
			emojiGroups = emojiGroups.filter((g) => g.id !== "favorites");
			console.log("[Userscript] Loaded data from localStorage:", {
				groupsCount: emojiGroups.length,
				emojisCount: emojiGroups.reduce((acc, g) => acc + (g.emojis?.length || 0), 0),
				settings
			});
			return {
				emojiGroups,
				settings
			};
		} catch (error) {
			console.error("[Userscript] Failed to load from localStorage:", error);
			return {
				emojiGroups: [],
				settings: { ...DEFAULT_USER_SETTINGS }
			};
		}
	}
	async function loadDataFromLocalStorageAsync(hostname) {
		try {
			const local = loadDataFromLocalStorage();
			if (local.emojiGroups && local.emojiGroups.length > 0) return local;
			const remoteUrl = localStorage.getItem("emoji_extension_remote_config_url");
			const configUrl = remoteUrl && typeof remoteUrl === "string" && remoteUrl.trim().length > 0 ? remoteUrl : "https://video2gif-pages.pages.dev/assets/defaultEmojiGroups.json";
			try {
				const groups = await loadAndFilterDefaultEmojiGroups(configUrl, hostname);
				if (groups && groups.length > 0) {
					try {
						localStorage.setItem(STORAGE_KEY, JSON.stringify(groups));
					} catch (e) {
						console.warn("[Userscript] Failed to persist fetched groups to localStorage", e);
					}
					return {
						emojiGroups: groups.filter((g) => g.id !== "favorites"),
						settings: local.settings
					};
				}
			} catch (err) {
				console.warn(`[Userscript] Failed to fetch config from ${configUrl}:`, err);
			}
			return {
				emojiGroups: [],
				settings: local.settings
			};
		} catch (error) {
			console.error("[Userscript] loadDataFromLocalStorageAsync failed:", error);
			return {
				emojiGroups: [],
				settings: { ...DEFAULT_USER_SETTINGS }
			};
		}
	}
	function saveDataToLocalStorage(data) {
		try {
			if (data.emojiGroups) localStorage.setItem(STORAGE_KEY, JSON.stringify(data.emojiGroups));
			if (data.settings) localStorage.setItem(SETTINGS_KEY, JSON.stringify(data.settings));
		} catch (error) {
			console.error("[Userscript] Failed to save to localStorage:", error);
		}
	}
	function syncFromManager() {
		try {
			const managerGroups = localStorage.getItem("emoji_extension_manager_groups");
			const managerSettings = localStorage.getItem("emoji_extension_manager_settings");
			let updated = false;
			if (managerGroups) {
				const groups = JSON.parse(managerGroups);
				if (Array.isArray(groups)) {
					localStorage.setItem(STORAGE_KEY, JSON.stringify(groups));
					updated = true;
				}
			}
			if (managerSettings) {
				const settings = JSON.parse(managerSettings);
				if (typeof settings === "object") {
					localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
					updated = true;
				}
			}
			if (updated) console.log("[Userscript] Synced data from manager");
			return updated;
		} catch (error) {
			console.error("[Userscript] Failed to sync from manager:", error);
			return false;
		}
	}
	function trackEmojiUsage(emojiName, emojiUrl) {
		try {
			const key = `${emojiName}|${emojiUrl}`;
			const statsData = localStorage.getItem(USAGE_STATS_KEY);
			let stats = {};
			if (statsData) try {
				stats = JSON.parse(statsData);
			} catch (e) {
				console.warn("[Userscript] Failed to parse usage stats:", e);
			}
			if (!stats[key]) stats[key] = {
				count: 0,
				lastUsed: 0
			};
			stats[key].count++;
			stats[key].lastUsed = Date.now();
			localStorage.setItem(USAGE_STATS_KEY, JSON.stringify(stats));
		} catch (error) {
			console.error("[Userscript] Failed to track emoji usage:", error);
		}
	}
	function getPopularEmojis(limit = 20) {
		try {
			const statsData = localStorage.getItem(USAGE_STATS_KEY);
			if (!statsData) return [];
			const stats = JSON.parse(statsData);
			return Object.entries(stats).map(([key, data]) => {
				const [name, url] = key.split("|");
				return {
					name,
					url,
					count: data.count,
					lastUsed: data.lastUsed
				};
			}).sort((a, b) => b.count - a.count).slice(0, limit);
		} catch (error) {
			console.error("[Userscript] Failed to get popular emojis:", error);
			return [];
		}
	}
	function clearEmojiUsageStats() {
		try {
			localStorage.removeItem(USAGE_STATS_KEY);
			console.log("[Userscript] Cleared emoji usage statistics");
		} catch (error) {
			console.error("[Userscript] Failed to clear usage stats:", error);
		}
	}
	var STORAGE_KEY, SETTINGS_KEY, USAGE_STATS_KEY, DEFAULT_USER_SETTINGS;
	var init_userscript_storage = __esmMin((() => {
		init_default_emoji_loader();
		STORAGE_KEY = "emoji_extension_userscript_data";
		SETTINGS_KEY = "emoji_extension_userscript_settings";
		USAGE_STATS_KEY = "emoji_extension_userscript_usage_stats";
		DEFAULT_USER_SETTINGS = {
			imageScale: 30,
			gridColumns: 4,
			outputFormat: "markdown",
			forceMobileMode: false,
			defaultGroup: "nachoneko",
			showSearchBar: true,
			enableFloatingPreview: true,
			enableCalloutSuggestions: true,
			enableBatchParseImages: true
		};
	}));
	var userscriptState;
	var init_state = __esmMin((() => {
		init_userscript_storage();
		userscriptState = {
			emojiGroups: [],
			settings: { ...DEFAULT_USER_SETTINGS },
			emojiUsageStats: {}
		};
	}));
	function detectRuntimePlatform() {
		try {
			const isMobileSize = window.innerWidth <= 768;
			const userAgent = navigator.userAgent.toLowerCase();
			const isMobileUserAgent = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent);
			const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0;
			if (isMobileSize && (isMobileUserAgent || isTouchDevice)) return "mobile";
			else if (!isMobileSize && !isMobileUserAgent) return "pc";
			return "original";
		} catch {
			return "original";
		}
	}
	function getEffectivePlatform() {
		return detectRuntimePlatform();
	}
	function getPlatformUIConfig() {
		switch (getEffectivePlatform()) {
			case "mobile": return {
				emojiPickerMaxHeight: "60vh",
				emojiPickerColumns: 4,
				emojiSize: 32,
				isModal: true,
				useCompactLayout: true,
				showSearchBar: true,
				floatingButtonSize: 48
			};
			case "pc": return {
				emojiPickerMaxHeight: "400px",
				emojiPickerColumns: 6,
				emojiSize: 24,
				isModal: false,
				useCompactLayout: false,
				showSearchBar: true,
				floatingButtonSize: 40
			};
			default: return {
				emojiPickerMaxHeight: "350px",
				emojiPickerColumns: 5,
				emojiSize: 28,
				isModal: false,
				useCompactLayout: false,
				showSearchBar: true,
				floatingButtonSize: 44
			};
		}
	}
	function logPlatformInfo() {
		const buildPlatform = "original";
		const runtimePlatform = detectRuntimePlatform();
		const effectivePlatform = getEffectivePlatform();
		const config = getPlatformUIConfig();
		console.log("[Platform] Build target:", buildPlatform);
		console.log("[Platform] Runtime detected:", runtimePlatform);
		console.log("[Platform] Effective platform:", effectivePlatform);
		console.log("[Platform] UI config:", config);
		console.log("[Platform] Screen size:", `${window.innerWidth}x${window.innerHeight}`);
		console.log("[Platform] User agent mobile:", /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase()));
		console.log("[Platform] Touch device:", "ontouchstart" in window || navigator.maxTouchPoints > 0);
	}
	var init_platformDetection = __esmMin((() => {}));
	init_state();
	const __vitePreload = function preload(baseModule, deps, importerUrl) {
		let promise = Promise.resolve();
		function handlePreloadError(err$2) {
			const e$1 = new Event("vite:preloadError", { cancelable: true });
			e$1.payload = err$2;
			window.dispatchEvent(e$1);
			if (!e$1.defaultPrevented) throw err$2;
		}
		return promise.then((res) => {
			for (const item of res || []) {
				if (item.status !== "rejected") continue;
				handlePreloadError(item.reason);
			}
			return baseModule().catch(handlePreloadError);
		});
	};
	function ensureStyleInjected(id, css) {
		const style = document.createElement("style");
		style.id = id;
		style.textContent = css;
		document.documentElement.appendChild(style);
	}
	var init_injectStyles = __esmMin((() => {}));
	function injectManagerStyles() {
		if (__managerStylesInjected) return;
		__managerStylesInjected = true;
		ensureStyleInjected("emoji-manager-styles", `
    /* Modal backdrop */
    .emoji-manager-wrapper { 
      position: fixed; 
      top: 0; 
      left: 0; 
      right: 0; 
      bottom: 0; 
      z-index: 999999; 
      display: flex; 
      align-items: center; 
      justify-content: center; 
      background: rgba(0, 0, 0, 0.5);
    }
    
    /* Main modal panel */
    .emoji-manager-panel { 
      border-radius: 8px; 
      width: 90%; 
      height: 95%; 
      display: grid; 
      grid-template-columns: 300px 1fr; 
      grid-template-rows: 1fr auto;
      overflow: hidden; 
      box-shadow: 0 10px 40px rgba(0,0,0,0.3); 
      background: var(--primary-low);
    }
    
    /* Mobile-specific styles */
    @media (max-width: 768px) {
      .emoji-manager-panel {
        width: 100%;
        height: 100%;
        border-radius: 0;
        grid-template-columns: 1fr;
        grid-template-rows: auto 1fr auto;
      }
    }
    
    /* Left panel - groups list */
    .emoji-manager-left { 
      background: var(--primary-very-low); 
      border-right: 1px solid #e9ecef; 
      display: flex; 
      flex-direction: column; 
      overflow: hidden; 
    }
    
    .emoji-manager-left-header { 
      display: flex; 
      align-items: center; 
      justify-content: space-between;
      padding: 16px; 
      background: var(--primary-low); 
    }
    
    .emoji-manager-left-header h3 {
      margin: 0;
      font-size: 18px;
      flex: 1;
    }
    
    /* Mobile: Left panel becomes a dropdown selector */
    @media (max-width: 768px) {
      .emoji-manager-left {
        border-right: none;
        border-bottom: 1px solid #e9ecef;
        max-height: none;
        overflow-y: visible;
      }
      
      .emoji-manager-left-header {
        padding: 12px 16px;
        position: sticky;
        top: 0;
        z-index: 10;
      }
      
      .emoji-manager-left-header h3 {
        font-size: 16px;
      }
      
      /* Hide the groups list on mobile */
      .emoji-manager-groups-list {
        display: none;
      }
      
      /* Hide add group row on mobile */
      .emoji-manager-addgroup-row {
        display: none;
      }
    }
    
    .emoji-manager-addgroup-row { 
      display: flex; 
      gap: 8px;
      padding: 12px; 
    }
    
    .emoji-manager-addgroup-row input {
      flex: 1;
      min-width: 0;
    }
    
    /* Group selector dropdown for mobile */
    .emoji-manager-group-selector {
      display: none;
      width: 100%;
      padding: 12px;
    }
    
    .emoji-manager-group-selector select {
      width: 100%;
      padding: 12px;
      font-size: 16px;
      border: 1px solid #dee2e6;
      border-radius: 6px;
      background: var(--primary-very-low);
      color: var(--primary);
    }
    
    /* Mobile: Larger touch targets for add group */
    @media (max-width: 768px) {
      .emoji-manager-group-selector {
        display: block;
      }
      
      .emoji-manager-addgroup-row {
        padding: 12px 16px;
      }
      
      .emoji-manager-addgroup-row input,
      .emoji-manager-addgroup-row button {
        font-size: 16px;
        padding: 12px;
      }
    }
    
    .emoji-manager-groups-list { 
      background: var(--primary-very-low);
      flex: 1; 
      overflow-y: auto; 
      padding: 8px; 
    }
    
    .emoji-manager-groups-list > div { 
      margin-bottom: 4px; 
      padding: 12px;
      border-radius: 6px;
      cursor: pointer;
      transition: background-color 0.2s; 
    }
    
    .emoji-manager-groups-list > div:hover { 
      background: var(--d-selected); 
    }
    
    .emoji-manager-groups-list > div:focus { 
      outline: none; 
      box-shadow: inset 0 0 0 2px #007bff; 
      background: var(--d-selected); 
    }
    
    /* Mobile: Larger touch targets for group items */
    @media (max-width: 768px) {
      .emoji-manager-groups-list {
        padding: 8px 16px;
      }
      
      .emoji-manager-groups-list > div {
        padding: 16px 12px;
        margin-bottom: 8px;
        font-size: 15px;
      }
    }
    
    /* Right panel - emoji display and editing */
    .emoji-manager-right { 
      background: var(--primary-low); 
      display: flex; 
      flex-direction: column; 
      overflow: hidden; 
    }
    
    .emoji-manager-right-header { 
      display: flex; 
      align-items: center; 
      justify-content: space-between; 
      padding: 16px; 
      background: var(--primary-very-low);
      border-bottom: 1px solid #e9ecef;
    }
    
    .emoji-manager-right-header h4 {
      margin: 0;
      font-size: 16px;
      flex: 1;
    }
    
    /* Mobile: Sticky header and larger buttons */
    @media (max-width: 768px) {
      .emoji-manager-right-header {
        padding: 12px 16px;
        position: sticky;
        top: 0;
        z-index: 10;
      }
      
      .emoji-manager-right-header h4 {
        font-size: 15px;
      }
      
      .emoji-manager-right-header button {
        padding: 10px 16px;
        font-size: 14px;
      }
    }
    
    .emoji-manager-right-main { 
      flex: 1; 
      overflow-y: auto; 
      padding: 16px;
    }
    
    .emoji-manager-emojis { 
      display: grid; 
      grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); 
      gap: 12px; 
      padding: 0;
    }
    
    /* Mobile: Optimize grid for smaller screens */
    @media (max-width: 768px) {
      .emoji-manager-right-main {
        padding: 12px;
      }
      
      .emoji-manager-emojis {
        grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
        gap: 10px;
      }
    }
    
    @media (max-width: 480px) {
      .emoji-manager-emojis {
        grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
        gap: 8px;
      }
    }
    
    .emoji-manager-card { 
      display: flex; 
      flex-direction: column; 
      align-items: center; 
      padding: 12px; 
      background: var(--primary-medium); 
      border-radius: 8px;
      transition: transform 0.2s, box-shadow 0.2s;
    }
    
    .emoji-manager-card:hover { 
      transform: translateY(-2px); 
      box-shadow: 0 4px 12px rgba(0,0,0,0.1); 
    }
    
    /* Mobile: Better touch targets and spacing */
    @media (max-width: 768px) {
      .emoji-manager-card {
        padding: 10px;
        border-radius: 6px;
      }
      
      /* Disable hover effects on mobile */
      .emoji-manager-card:hover {
        transform: none;
        box-shadow: none;
      }
      
      /* Add active state for touch feedback */
      .emoji-manager-card:active {
        transform: scale(0.98);
        box-shadow: 0 2px 8px rgba(0,0,0,0.15);
      }
    }
    
    .emoji-manager-card-img { 
      max-width: 90%;
      max-height: 80px;
      object-fit: contain; 
      border-radius: 6px; 
      background: white; 
      margin-bottom: 8px;
    }
    
    .emoji-manager-card-name { 
      font-size: 12px; 
      color: var(--primary); 
      text-align: center; 
      width: 100%; 
      overflow: hidden; 
      white-space: nowrap; 
      text-overflow: ellipsis; 
      font-weight: 500; 
      margin-bottom: 8px;
    }
    
    .emoji-manager-card-actions { 
      display: flex; 
      gap: 6px; 
      width: 100%;
      justify-content: center;
    }
    
    /* Mobile: Larger buttons for touch */
    @media (max-width: 768px) {
      .emoji-manager-card-img {
        max-height: 70px;
        margin-bottom: 6px;
      }
      
      .emoji-manager-card-name {
        font-size: 11px;
        margin-bottom: 6px;
      }
      
      .emoji-manager-card-actions {
        gap: 8px;
      }
      
      .emoji-manager-card-actions .btn-sm {
        padding: 8px 12px;
        font-size: 13px;
        min-height: 36px;
        flex: 1;
      }
    }
    
    /* Add emoji form */
    .emoji-manager-add-emoji-form { 
      padding: 16px; 
      background: var(--primary-very-low); 
      border-top: 1px solid #e9ecef; 
      display: flex; 
      flex-wrap: wrap;
      gap: 8px; 
      align-items: center; 
    }
    
    .emoji-manager-add-emoji-form input {
      flex: 1;
      min-width: 150px;
    }
    
    .emoji-manager-add-emoji-form button {
      white-space: nowrap;
    }
    
    /* Mobile: Stack inputs vertically */
    @media (max-width: 768px) {
      .emoji-manager-add-emoji-form {
        padding: 12px 16px;
        flex-direction: column;
        align-items: stretch;
      }
      
      .emoji-manager-add-emoji-form input {
        width: 100%;
        min-width: 0;
        font-size: 16px;
        padding: 12px;
      }
      
      .emoji-manager-add-emoji-form button {
        width: 100%;
        padding: 12px;
        font-size: 15px;
        min-height: 44px;
      }
    }
    
    /* Footer */
    .emoji-manager-footer { 
      grid-column: 1 / -1;
      display: flex; 
      flex-wrap: wrap;
      justify-content: space-between; 
      gap: 12px;
      padding: 16px; 
      background: var(--primary-very-low); 
      border-top: 1px solid #e9ecef;
    }
    
    .emoji-manager-footer button {
      flex: 0 1 auto;
    }
    
    /* Mobile: Stack footer buttons */
    @media (max-width: 768px) {
      .emoji-manager-footer {
        padding: 12px 16px;
        flex-direction: column;
      }
      
      .emoji-manager-footer button {
        width: 100%;
        padding: 12px;
        font-size: 15px;
        min-height: 44px;
      }
    }
    
    /* Editor panel - popup modal */
    .emoji-manager-editor-panel { 
      position: fixed; 
      top: 50%; 
      left: 50%; 
      transform: translate(-50%, -50%); 
      background: var(--primary-medium); 
      padding: 24px; 
      border-radius: 8px;
      z-index: 1000000; 
      max-width: 90%;
      max-height: 90vh;
      overflow-y: auto;
      box-shadow: 0 10px 40px rgba(0,0,0,0.3);
    }
    
    .emoji-manager-editor-panel input,
    .emoji-manager-editor-panel button {
      margin: 8px 0;
    }
    
    .emoji-manager-editor-preview { 
      max-width: 100%;
      max-height: 40vh;
      object-fit: contain;
      display: block;
      margin: 12px auto;
      border-radius: 6px;
      background: white;
    }
    
    /* Mobile: Full-width editor on small screens */
    @media (max-width: 768px) {
      .emoji-manager-editor-panel {
        width: calc(100% - 32px);
        max-width: none;
        padding: 20px;
        border-radius: 12px;
      }
      
      .emoji-manager-editor-panel input {
        font-size: 16px;
        padding: 12px;
        margin: 6px 0;
      }
      
      .emoji-manager-editor-panel button {
        padding: 12px;
        font-size: 15px;
        min-height: 44px;
      }
      
      .emoji-manager-editor-preview {
        max-height: 30vh;
      }
    }
    
    @media (max-width: 480px) {
      .emoji-manager-editor-panel {
        width: calc(100% - 16px);
        padding: 16px;
        max-height: 95vh;
      }
    }

    /* Hover preview (moved from inline styles) */
    .emoji-manager-hover-preview {
      position: fixed;
      pointer-events: none;
      z-index: 1000002;
      display: none;
      max-width: 60%;
      max-height: 60%;
      border: 1px solid rgba(0,0,0,0.1);
      object-fit: contain;
      background: var(--primary);
      padding: 4px;
      border-radius: 6px;
      box-shadow: 0 6px 18px rgba(0,0,0,0.12);
    }
    
    /* Mobile: Disable hover preview */
    @media (max-width: 768px) {
      .emoji-manager-hover-preview {
        display: none !important;
      }
    }
    
    /* Form styling */
    .form-control { 
      width: 100%; 
      padding: 8px 12px;
      border: 1px solid #dee2e6;
      border-radius: 4px;
      font-size: 14px;
      line-height: 1.5;
      background: var(--primary-very-low);
      color: var(--primary);
    }
    
    .form-control:focus {
      outline: none;
      border-color: #007bff;
      box-shadow: 0 0 0 2px rgba(0,123,255,0.25);
    }
    
    .btn { 
      padding: 8px 16px; 
      border: 1px solid transparent; 
      border-radius: 4px; 
      font-size: 14px; 
      font-weight: 500;
      cursor: pointer; 
      transition: all 0.2s; 
      color: var(--primary);
    }
    
    .btn:hover {
      transform: translateY(-1px);
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }
    
    .btn:active {
      transform: translateY(0);
    }
    
    .btn-primary {
      background: #007bff;
      color: #fff;
    }
    
    .btn-primary:hover {
      background: #0056b3;
    }
    
    .btn-sm { 
      padding: 6px 12px; 
      font-size: 13px; 
    }
    
    /* Mobile: Larger touch targets for buttons */
    @media (max-width: 768px) {
      .form-control {
        font-size: 16px;
        padding: 10px 12px;
      }
      
      .btn {
        min-height: 44px;
        padding: 10px 16px;
        font-size: 15px;
      }
      
      .btn:hover {
        transform: none;
      }
      
      .btn:active {
        transform: scale(0.98);
      }
      
      .btn-sm {
        padding: 8px 12px;
        font-size: 14px;
        min-height: 36px;
      }
    }
  `);
	}
	var __managerStylesInjected;
	var init_styles = __esmMin((() => {
		init_injectStyles();
		__managerStylesInjected = false;
	}));
	function createEl(tag, opts) {
		const el = document.createElement(tag);
		if (opts) {
			if (opts.width) el.style.width = opts.width;
			if (opts.height) el.style.height = opts.height;
			if (opts.className) el.className = opts.className;
			if (opts.text) el.textContent = opts.text;
			if (opts.placeholder && "placeholder" in el) el.placeholder = opts.placeholder;
			if (opts.type && "type" in el) el.type = opts.type;
			if (opts.value !== void 0 && "value" in el) el.value = opts.value;
			if (opts.style) el.style.cssText = opts.style;
			if (opts.src && "src" in el) el.src = opts.src;
			if (opts.attrs) for (const k in opts.attrs) el.setAttribute(k, opts.attrs[k]);
			if (opts.dataset) for (const k in opts.dataset) el.dataset[k] = opts.dataset[k];
			if (opts.innerHTML) el.innerHTML = opts.innerHTML;
			if (opts.title) el.title = opts.title;
			if (opts.alt && "alt" in el) el.alt = opts.alt;
			if (opts.id) el.id = opts.id;
			if (opts.on) for (const [evt, handler] of Object.entries(opts.on)) el.addEventListener(evt, handler);
		}
		return el;
	}
	var init_createEl = __esmMin((() => {}));
	function ensureHoverPreview() {
		if (_sharedPreview && document.body.contains(_sharedPreview)) return _sharedPreview;
		_sharedPreview = createEl("div", {
			className: "emoji-picker-hover-preview",
			style: "position:fixed;pointer-events:none;display:none;z-index:1000002;max-width:300px;max-height:300px;overflow:hidden;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.25);background:transparent;padding:6px;"
		});
		const img = createEl("img", {
			className: "emoji-picker-hover-img",
			style: "display:block;max-width:100%;max-height:220px;object-fit:contain;"
		});
		const label = createEl("div", {
			className: "emoji-picker-hover-label",
			style: "font-size:12px;color:var(--primary);margin-top:6px;text-align:center;"
		});
		_sharedPreview.appendChild(img);
		_sharedPreview.appendChild(label);
		document.body.appendChild(_sharedPreview);
		return _sharedPreview;
	}
	var _sharedPreview;
	var init_hoverPreview = __esmMin((() => {
		init_createEl();
		_sharedPreview = null;
	}));
	function injectGlobalThemeStyles() {
		if (themeStylesInjected || typeof document === "undefined") return;
		themeStylesInjected = true;
		document.head.appendChild(createEl("style", {
			id: "emoji-extension-theme-globals",
			text: `
    /* Global CSS variables for emoji extension theme support */
    :root {
      /* Light theme (default) */
      --emoji-modal-bg: #ffffff;
      --emoji-modal-text: #333333;
      --emoji-modal-border: #dddddd;
      --emoji-modal-input-bg: #ffffff;
      --emoji-modal-label: #555555;
      --emoji-modal-button-bg: #f5f5f5;
      --emoji-modal-primary-bg: #1890ff;
      
      --emoji-preview-bg: #ffffff;
      --emoji-preview-text: #222222;
      --emoji-preview-border: rgba(0,0,0,0.08);
      
      --emoji-button-gradient-start: #667eea;
      --emoji-button-gradient-end: #764ba2;
      --emoji-button-shadow: rgba(0, 0, 0, 0.15);
      --emoji-button-hover-shadow: rgba(0, 0, 0, 0.2);
    }
    
    /* Dark theme */
    @media (prefers-color-scheme: dark) {
      :root {
        --emoji-modal-bg: #2d2d2d;
        --emoji-modal-text: #e6e6e6;
        --emoji-modal-border: #444444;
        --emoji-modal-input-bg: #3a3a3a;
        --emoji-modal-label: #cccccc;
        --emoji-modal-button-bg: #444444;
        --emoji-modal-primary-bg: #1677ff;
        
        --emoji-preview-bg: rgba(32,33,36,0.94);
        --emoji-preview-text: #e6e6e6;
        --emoji-preview-border: rgba(255,255,255,0.12);
        
        --emoji-button-gradient-start: #4a5568;
        --emoji-button-gradient-end: #2d3748;
        --emoji-button-shadow: rgba(0, 0, 0, 0.3);
        --emoji-button-hover-shadow: rgba(0, 0, 0, 0.4);
      }
    }
  `
		}));
	}
	var themeStylesInjected;
	var init_themeSupport = __esmMin((() => {
		init_createEl();
		themeStylesInjected = false;
	}));
	function showTemporaryMessage(message, duration = 2e3) {
		const messageEl = createEl("div", {
			style: `
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background: var(--emoji-modal-primary-bg);
      color: white;
      padding: 12px 24px;
      border-radius: 6px;
      z-index: 9999999;
      font-size: 14px;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
      animation: fadeInOut 2s ease-in-out;
    `,
			text: message
		});
		if (!document.querySelector("#tempMessageStyles")) {
			const style = createEl("style", {
				id: "tempMessageStyles",
				text: `
      @keyframes fadeInOut {
        0%, 100% { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
        20%, 80% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
      }
    `
			});
			document.head.appendChild(style);
		}
		document.body.appendChild(messageEl);
		setTimeout(() => {
			try {
				messageEl.remove();
			} catch {}
		}, duration);
	}
	var init_tempMessage = __esmMin((() => {
		init_createEl();
	}));
	function createModalElement(options) {
		const modal = createEl("div", {
			style: `
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 999999;
      display: flex;
      align-items: center;
      justify-content: center;
    `,
			className: options.className
		});
		const content = createEl("div", { style: `
      background: var(--secondary);
      color: var(--emoji-modal-text);
      border: 1px solid var(--emoji-modal-border);
      border-radius: 8px;
      padding: 24px;
      max-width: 90%;
      max-height: 90%;
      overflow-y: auto;
      position: relative;
    ` });
		if (options.title) {
			const titleElement = createEl("div", {
				style: `
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 20px;
      `,
				innerHTML: `
        <h2 style="margin: 0; color: var(--emoji-modal-text);">${options.title}</h2>
        <button id="closeModal" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #999;">×</button>
      `
			});
			content.appendChild(titleElement);
			const closeButton = content.querySelector("#closeModal");
			if (closeButton && options.onClose) closeButton.addEventListener("click", options.onClose);
		}
		if (options.content) {
			const contentDiv = createEl("div", { innerHTML: options.content });
			content.appendChild(contentDiv);
		}
		modal.appendChild(content);
		return modal;
	}
	var init_editorUtils = __esmMin((() => {
		init_createEl();
	}));
	var importExport_exports = /* @__PURE__ */ __export({ showImportExportModal: () => showImportExportModal });
	function showImportExportModal(currentGroupId) {
		injectGlobalThemeStyles();
		const currentGroup = currentGroupId ? userscriptState.emojiGroups.find((g) => g.id === currentGroupId) : null;
		const modal = createModalElement({
			title: "分组表情导入/导出",
			content: `
    ${currentGroup ? `
    <div style="margin-bottom: 24px; padding: 16px; background: var(--emoji-modal-button-bg); border-radius: 8px;">
      <h3 style="margin: 0 0 12px 0; color: var(--emoji-modal-label);">当前分组信息</h3>
      <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
        ${currentGroup.icon?.startsWith("http") ? `<img src="${currentGroup.icon}" alt="图标" style="width: 24px; height: 24px; object-fit: contain;">` : `<span style="font-size: 20px;">${currentGroup.icon || "📁"}</span>`}
        <span style="font-weight: bold; color: var(--emoji-modal-text);">${currentGroup.name || currentGroup.id}</span>
      </div>
      <div style="color: var(--emoji-modal-text); font-size: 14px;">
        分组 ID: ${currentGroup.id} | 表情数量:${currentGroup.emojis?.length || 0}
      </div>
    </div>
    ` : ""}

    <div style="margin-bottom: 24px;">
      <h3 style="margin: 0 0 12px 0; color: var(--emoji-modal-label);">导出分组表情</h3>
      
      <div style="margin-bottom: 16px;">
        <label style="display: block; margin-bottom: 8px; color: var(--emoji-modal-label);">选择要导出的分组:</label>
        <select id="exportGroupSelect" style="
          width: 100%;
          padding: 8px;
          background: var(--emoji-modal-button-bg);
          color: var(--emoji-modal-text);
          border: 1px solid var(--emoji-modal-border);
          border-radius: 4px;
          margin-bottom: 8px;
        ">
          ${currentGroup ? `<option value="${currentGroup.id}" selected>${currentGroup.name || currentGroup.id} (${currentGroup.emojis?.length || 0} 表情)</option>` : ""}
          ${userscriptState.emojiGroups.filter((g) => g.id !== currentGroupId).map((group) => `<option value="${group.id}">${group.name || group.id} (${group.emojis?.length || 0} 表情)</option>`).join("")}
        </select>
      </div>

      <div style="display: flex; gap: 8px;">
        <button id="exportGroup" style="padding: 8px 16px; background: var(--emoji-modal-primary-bg); color: white; border: none; border-radius: 4px; cursor: pointer;">导出选中分组</button>
      </div>
    </div>

    <div>
      <h3 style="margin: 0 0 12px 0; color: var(--emoji-modal-label);">导入分组表情</h3>
      
      <div style="margin-bottom: 16px;">
        <label style="display: block; margin-bottom: 8px; color: var(--emoji-modal-label);">导入目标分组:</label>
        <select id="importTargetGroupSelect" style="
          width: 100%;
          padding: 8px;
          background: var(--emoji-modal-button-bg);
          color: var(--emoji-modal-text);
          border: 1px solid var(--emoji-modal-border);
          border-radius: 4px;
          margin-bottom: 8px;
        ">
          ${currentGroup ? `<option value="${currentGroup.id}" selected>${currentGroup.name || currentGroup.id}</option>` : ""}
          ${userscriptState.emojiGroups.filter((g) => g.id !== currentGroupId).map((group) => `<option value="${group.id}">${group.name || group.id}</option>`).join("")}
          <option value="__new__">创建新分组...</option>
        </select>
      </div>

      <div style="margin-bottom: 16px;">
        <label style="display: block; margin-bottom: 8px; color: var(--emoji-modal-label);">上传分组文件:</label>
        <input type="file" id="importFile" accept=".json" style="margin-bottom: 8px; color: var(--emoji-modal-text);">
        <div style="font-size: 12px; color: var(--emoji-modal-text); opacity: 0.7;">
          支持 JSON 格式的分组文件
        </div>
      </div>

      <div style="margin-bottom: 16px;">
        <label style="display: block; margin-bottom: 8px; color: var(--emoji-modal-label);">或粘贴分组 JSON:</label>
        <textarea id="importText" placeholder="在此粘贴分组表情 JSON..." style="
          width: 100%;
          height: 120px;
          padding: 8px;
          background: var(--emoji-modal-button-bg);
          color: var(--emoji-modal-text);
          border: 1px solid var(--emoji-modal-border);
          border-radius: 4px;
          resize: vertical;
          font-family: monospace;
          font-size: 12px;
        "></textarea>
      </div>

      <div style="margin-bottom: 16px;">
        <label style="display: block; margin-bottom: 8px; color: var(--emoji-modal-label);">导入选项:</label>
        <div style="display: flex; flex-direction: column; gap: 8px;">
          <label style="display: flex; align-items: center; color: var(--emoji-modal-text);">
            <input type="radio" name="importMode" value="replace" checked style="margin-right: 8px;">
            替换现有表情 (清空目标分组后导入)
          </label>
          <label style="display: flex; align-items: center; color: var(--emoji-modal-text);">
            <input type="radio" name="importMode" value="merge" style="margin-right: 8px;">
            合并表情 (添加到现有表情中,跳过重复的)
          </label>
          <label style="display: flex; align-items: center; color: var(--emoji-modal-text);">
            <input type="radio" name="importMode" value="append" style="margin-right: 8px;">
            追加表情 (直接添加到现有表情后面)
          </label>
        </div>
      </div>

      <div style="display: flex; gap: 8px;">
        <button id="importGroup" style="padding: 8px 16px; background: var(--emoji-modal-primary-bg); color: white; border: none; border-radius: 4px; cursor: pointer;">导入到分组</button>
        <button id="previewImport" style="padding: 8px 16px; background: var(--emoji-modal-button-bg); color: var(--emoji-modal-text); border: 1px solid var(--emoji-modal-border); border-radius: 4px; cursor: pointer;">预览导入</button>
      </div>
    </div>
  `,
			onClose: () => modal.remove()
		});
		const content = modal.querySelector("div:last-child");
		document.body.appendChild(modal);
		function createDownload(data, filename) {
			const jsonString = JSON.stringify(data, null, 2);
			const blob = new Blob([jsonString], { type: "application/json" });
			const url = URL.createObjectURL(blob);
			const a = document.createElement("a");
			a.href = url;
			a.download = filename;
			document.body.appendChild(a);
			a.click();
			document.body.removeChild(a);
			URL.revokeObjectURL(url);
		}
		function parseImportData(jsonData) {
			try {
				const data = JSON.parse(jsonData);
				if (!data || typeof data !== "object") throw new Error("无效的 JSON 格式");
				return data;
			} catch (error) {
				throw new Error("JSON 解析失败:" + (error instanceof Error ? error.message : String(error)));
			}
		}
		content.querySelector("#exportGroup")?.addEventListener("click", () => {
			try {
				const selectedGroupId = content.querySelector("#exportGroupSelect").value;
				if (!selectedGroupId) {
					alert("请选择要导出的分组");
					return;
				}
				const group = userscriptState.emojiGroups.find((g) => g.id === selectedGroupId);
				if (!group) {
					alert("找不到指定的分组");
					return;
				}
				const exportData = {
					type: "emoji_group",
					exportDate: (/* @__PURE__ */ new Date()).toISOString(),
					group: {
						id: group.id,
						name: group.name,
						icon: group.icon,
						emojis: group.emojis || [],
						order: group.order
					}
				};
				const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-");
				createDownload(exportData, `emoji-group-${group.name || group.id}-${timestamp}.json`);
				showTemporaryMessage(`已导出分组 "${group.name || group.id}" (${group.emojis?.length || 0} 个表情)`);
			} catch (error) {
				console.error("Export group failed:", error);
				alert("导出分组失败:" + (error instanceof Error ? error.message : String(error)));
			}
		});
		content.querySelector("#importFile")?.addEventListener("change", (e) => {
			const file = e.target.files?.[0];
			if (file) {
				const reader = new FileReader();
				reader.onload = (event) => {
					const text = event.target?.result;
					const importTextarea = content.querySelector("#importText");
					if (importTextarea) importTextarea.value = text;
				};
				reader.onerror = () => {
					alert("文件读取失败");
				};
				reader.readAsText(file);
			}
		});
		content.querySelector("#previewImport")?.addEventListener("click", () => {
			try {
				const importText = content.querySelector("#importText").value.trim();
				if (!importText) {
					alert("请输入或选择要导入的内容");
					return;
				}
				const data = parseImportData(importText);
				let preview = "导入预览:\\n\\n";
				if (data.type === "emoji_group" && data.group) {
					const group = data.group;
					preview += `分组类型:单个表情分组\\n`;
					preview += `分组名称:${group.name || group.id || "Unnamed"}\\n`;
					preview += `分组 ID: ${group.id || "N/A"}\\n`;
					preview += `图标:${group.icon || "无"}\\n`;
					preview += `表情数量:${group.emojis?.length || 0}\\n\\n`;
					if (group.emojis && group.emojis.length > 0) {
						preview += `表情列表 (前 5 个):\\n`;
						group.emojis.slice(0, 5).forEach((emoji, index) => {
							preview += `  ${index + 1}. ${emoji.name || "Unnamed"} - ${emoji.url || "No URL"}\\n`;
						});
						if (group.emojis.length > 5) preview += `  ... 还有 ${group.emojis.length - 5} 个表情\\n`;
					}
				} else if (data.emojiGroups && Array.isArray(data.emojiGroups)) {
					preview += `分组类型:多个表情分组\\n`;
					preview += `分组数量:${data.emojiGroups.length}\\n\\n`;
					data.emojiGroups.slice(0, 3).forEach((group, index) => {
						preview += `${index + 1}. ${group.name || group.id || "Unnamed"} (${group.emojis?.length || 0} 表情)\\n`;
					});
					if (data.emojiGroups.length > 3) preview += `... 还有 ${data.emojiGroups.length - 3} 个分组\\n`;
				} else if (Array.isArray(data) && data.length > 0 && data[0].id && data[0].url) {
					preview += `分组类型:表情数组 (带扩展字段)\\n`;
					preview += `表情数量:${data.length}\\n\\n`;
					const groupIds = [...new Set(data.map((emoji) => emoji.groupId).filter(Boolean))];
					if (groupIds.length > 0) preview += `包含的原始分组 ID: ${groupIds.join(", ")}\\n\\n`;
					if (data.length > 0) {
						preview += `表情列表 (前 5 个):\\n`;
						data.slice(0, 5).forEach((emoji, index) => {
							preview += `  ${index + 1}. ${emoji.name || emoji.id} - ${emoji.url}\\n`;
							if (emoji.groupId) preview += `     原分组:${emoji.groupId}\\n`;
						});
						if (data.length > 5) preview += `  ... 还有 ${data.length - 5} 个表情\\n`;
					}
				} else preview += "无法识别的格式,可能不是有效的分组导出文件";
				alert(preview);
			} catch (error) {
				alert("预览失败:" + (error instanceof Error ? error.message : String(error)));
			}
		});
		content.querySelector("#importGroup")?.addEventListener("click", () => {
			try {
				const importText = content.querySelector("#importText").value.trim();
				if (!importText) {
					alert("请输入或选择要导入的内容");
					return;
				}
				let targetGroupId = content.querySelector("#importTargetGroupSelect").value;
				if (targetGroupId === "__new__") {
					const newGroupName = prompt("请输入新分组的名称:");
					if (!newGroupName || !newGroupName.trim()) return;
					const newGroupId = "imported_" + Date.now();
					const newGroup = {
						id: newGroupId,
						name: newGroupName.trim(),
						icon: "📁",
						emojis: [],
						order: userscriptState.emojiGroups.length
					};
					userscriptState.emojiGroups.push(newGroup);
					targetGroupId = newGroupId;
				}
				if (!targetGroupId) {
					alert("请选择目标分组");
					return;
				}
				const targetGroup = userscriptState.emojiGroups.find((g) => g.id === targetGroupId);
				if (!targetGroup) {
					alert("找不到目标分组");
					return;
				}
				const data = parseImportData(importText);
				const importModeInputs = content.querySelectorAll("input[name=\"importMode\"]");
				const importMode = Array.from(importModeInputs).find((input) => input.checked)?.value || "replace";
				let importedEmojis = [];
				if (data.type === "emoji_group" && data.group && data.group.emojis) importedEmojis = data.group.emojis;
				else if (data.emojiGroups && Array.isArray(data.emojiGroups)) importedEmojis = data.emojiGroups.reduce((acc, group) => {
					return acc.concat(group.emojis || []);
				}, []);
				else if (Array.isArray(data.emojis)) importedEmojis = data.emojis;
				else if (Array.isArray(data) && data.length > 0 && data[0].id && data[0].url) importedEmojis = data.map((emoji) => ({
					name: emoji.name || emoji.id || "unnamed",
					url: emoji.url,
					width: emoji.width,
					height: emoji.height,
					originalId: emoji.id,
					packet: emoji.packet,
					originalGroupId: emoji.groupId
				}));
				else {
					alert("无法识别的导入格式");
					return;
				}
				if (importedEmojis.length === 0) {
					alert("导入文件中没有找到表情数据");
					return;
				}
				let finalEmojis = [];
				switch (importMode) {
					case "replace":
						finalEmojis = importedEmojis;
						break;
					case "merge":
						const existingUrls = new Set((targetGroup.emojis || []).map((e) => e.url));
						const existingIds = new Set((targetGroup.emojis || []).map((e) => e.originalId || e.id).filter(Boolean));
						const newEmojis = importedEmojis.filter((e) => {
							if (existingUrls.has(e.url)) return false;
							if (e.originalId && existingIds.has(e.originalId)) return false;
							return true;
						});
						finalEmojis = [...targetGroup.emojis || [], ...newEmojis];
						break;
					case "append":
						finalEmojis = [...targetGroup.emojis || [], ...importedEmojis];
						break;
					default: finalEmojis = importedEmojis;
				}
				targetGroup.emojis = finalEmojis;
				saveDataToLocalStorage({ emojiGroups: userscriptState.emojiGroups });
				const message = `成功导入 ${importedEmojis.length} 个表情到分组 "${targetGroup.name || targetGroup.id}"`;
				showTemporaryMessage(message);
				alert(message + "\\n\\n修改已保存,分组现在共有 " + finalEmojis.length + " 个表情");
				modal.remove();
			} catch (error) {
				console.error("Import group failed:", error);
				alert("导入分组失败:" + (error instanceof Error ? error.message : String(error)));
			}
		});
	}
	var init_importExport = __esmMin((() => {
		init_userscript_storage();
		init_themeSupport();
		init_tempMessage();
		init_editorUtils();
	}));
	function customAlert(message) {
		return new Promise((resolve) => {
			const backdrop = createEl("div", { style: `
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        z-index: 2147483646;
        display: flex;
        align-items: center;
        justify-content: center;
      ` });
			const dialog = createEl("div", { style: `
        background: #ffffff;
        color: #000000;
        padding: 20px;
        border-radius: 8px;
        max-width: 90vw;
        max-height: 80vh;
        overflow-y: auto;
        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
        text-align: center;
        min-width: 300px;
      ` });
			const messageEl = createEl("div", {
				text: message,
				style: "margin-bottom: 20px; word-wrap: break-word;"
			});
			const okButton = createEl("button", {
				text: "确定",
				className: "btn btn-primary",
				style: "padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; background: #007bff; color: white;"
			});
			okButton.addEventListener("click", () => {
				backdrop.remove();
				resolve();
			});
			dialog.appendChild(messageEl);
			dialog.appendChild(okButton);
			backdrop.appendChild(dialog);
			document.body.appendChild(backdrop);
			const handleEsc = (e) => {
				if (e.key === "Escape") {
					backdrop.remove();
					document.removeEventListener("keydown", handleEsc);
					resolve();
				}
			};
			document.addEventListener("keydown", handleEsc);
			backdrop.addEventListener("click", (e) => {
				if (e.target === backdrop) {
					backdrop.remove();
					document.removeEventListener("keydown", handleEsc);
					resolve();
				}
			});
		});
	}
	function customConfirm$1(message) {
		return new Promise((resolve) => {
			const backdrop = createEl("div", { style: `
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        z-index: 2147483646;
        display: flex;
        align-items: center;
        justify-content: center;
      ` });
			const dialog = createEl("div", { style: `
        background: #ffffff;
        color: #000000;
        padding: 20px;
        border-radius: 8px;
        max-width: 90vw;
        max-height: 80vh;
        overflow-y: auto;
        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
        text-align: center;
        min-width: 300px;
      ` });
			const messageEl = createEl("div", {
				text: message,
				style: "margin-bottom: 20px; word-wrap: break-word;"
			});
			const buttonContainer = createEl("div", { style: "display: flex; gap: 10px; justify-content: center;" });
			const cancelButton = createEl("button", {
				text: "取消",
				className: "btn",
				style: "padding: 8px 16px; border: 1px solid #ccc; border-radius: 4px; cursor: pointer; background: #f8f9fa; color: #333;"
			});
			const okButton = createEl("button", {
				text: "确定",
				className: "btn btn-primary",
				style: "padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; background: #007bff; color: white;"
			});
			cancelButton.addEventListener("click", () => {
				backdrop.remove();
				resolve(false);
			});
			okButton.addEventListener("click", () => {
				backdrop.remove();
				resolve(true);
			});
			buttonContainer.appendChild(cancelButton);
			buttonContainer.appendChild(okButton);
			dialog.appendChild(messageEl);
			dialog.appendChild(buttonContainer);
			backdrop.appendChild(dialog);
			document.body.appendChild(backdrop);
			const handleEsc = (e) => {
				if (e.key === "Escape") {
					backdrop.remove();
					document.removeEventListener("keydown", handleEsc);
					resolve(false);
				}
			};
			document.addEventListener("keydown", handleEsc);
		});
	}
	function customPrompt(message, defaultValue = "") {
		return new Promise((resolve) => {
			const backdrop = createEl("div", { style: `
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        z-index: 2147483646;
        display: flex;
        align-items: center;
        justify-content: center;
      ` });
			const dialog = createEl("div", { style: `
        background: #ffffff;
        color: #000000;
        padding: 20px;
        border-radius: 8px;
        max-width: 90vw;
        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
        text-align: center;
        min-width: 300px;
      ` });
			const messageEl = createEl("div", {
				text: message,
				style: "margin-bottom: 15px; word-wrap: break-word;"
			});
			const input = createEl("input", {
				attrs: {
					type: "text",
					value: defaultValue
				},
				className: "form-control",
				style: `
        width: 100%;
        padding: 8px 12px;
        margin-bottom: 15px;
        border: 1px solid #ccc;
        border-radius: 4px;
        box-sizing: border-box;
      `
			});
			const buttonContainer = createEl("div", { style: "display: flex; gap: 10px; justify-content: center;" });
			const cancelButton = createEl("button", {
				text: "取消",
				className: "btn",
				style: "padding: 8px 16px; border: 1px solid #ccc; border-radius: 4px; cursor: pointer; background: #f8f9fa; color: #333;"
			});
			const okButton = createEl("button", {
				text: "确定",
				className: "btn btn-primary",
				style: "padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; background: #007bff; color: white;"
			});
			cancelButton.addEventListener("click", () => {
				backdrop.remove();
				resolve(null);
			});
			okButton.addEventListener("click", () => {
				backdrop.remove();
				resolve(input.value);
			});
			setTimeout(() => {
				input.focus();
				input.select();
			}, 10);
			input.addEventListener("keydown", (e) => {
				if (e.key === "Enter") {
					backdrop.remove();
					resolve(input.value);
				}
			});
			buttonContainer.appendChild(cancelButton);
			buttonContainer.appendChild(okButton);
			dialog.appendChild(messageEl);
			dialog.appendChild(input);
			dialog.appendChild(buttonContainer);
			backdrop.appendChild(dialog);
			document.body.appendChild(backdrop);
			const handleEsc = (e) => {
				if (e.key === "Escape") {
					backdrop.remove();
					document.removeEventListener("keydown", handleEsc);
					resolve(null);
				}
			};
			document.addEventListener("keydown", handleEsc);
		});
	}
	var init_dialog = __esmMin((() => {
		init_createEl();
	}));
	var init_utils = __esmMin((() => {
		init_dialog();
	}));
	function showGroupEditorModal() {
		injectGlobalThemeStyles();
		const modal = createModalElement({
			title: "表情分组编辑器",
			content: `
    <div style="margin-bottom: 20px; padding: 16px; background: var(--emoji-modal-button-bg);">
      <div>编辑说明</div>
      <div>
        • 点击分组名称或图标进行编辑<br>
        • 图标支持 emoji 字符或单个字符<br>
        • 修改会立即保存到本地存储<br>
        • 使用上移/下移按钮调整分组的显示顺序
      </div>
    </div>
    
    <div id="groupsList" style="display: flex; flex-wrap: wrap; gap: 16px; max-height: 70vh; overflow-y: auto; justify-content: flex-start;">
      ${userscriptState.emojiGroups.map((group, index) => `
        <div class="group-item" data-group-id="${group.id}" data-index="${index}" style="
          display: flex;
          flex-direction: column;
          gap: 12px;
          padding: 16px;
          background: var(--emoji-modal-button-bg);
          border: 1px solid var(--emoji-modal-border);
          border-radius: 8px;
          width: calc(20% - 13px);
          min-width: 200px;
          box-sizing: border-box;
        ">
          <div style="display: flex; align-items: center; gap: 8px; justify-content: flex-end;">
            <button class="delete-group" data-index="${index}" data-group-id="${group.id}" data-group-name="${group.name}" style="
              background: #dc3545;
              border: 1px solid #c82333;
              border-radius: 3px;
              padding: 4px 8px;
              cursor: pointer;
              font-size: 12px;
              color: white;
            " title="删除分组">🗑️</button>
          </div>` + (group.icon?.startsWith("https://") ? `<img class="group-icon-editor" src="${group.icon}" alt="图标" style="
            width: 100%;
            height: 100px;
            object-fit: contain;
            cursor: pointer;
          " data-group-id="${group.id}" title="点击编辑图标">` : `
          <div class="group-icon-editor" style="
            display: flex;
            align-items: center;
            justify-content: center;
            background: var(--secondary);
            font-size: 48px;
            user-select: none;
            cursor: pointer;
            height: 100px;
            border-radius: 6px;
          " data-group-id="${group.id}" title="点击编辑图标">
            ${group.icon || "📁"}
          </div>`) + `<div style="display: flex; flex-direction: column; gap: 8px;">
            <input class="group-name-editor" 
                   type="text" 
                   value="${group.name || "Unnamed Group"}" 
                   data-group-id="${group.id}"
                   style="
                     background: var(--secondary);
                     color: var(--emoji-modal-text);
                     border: 1px solid var(--emoji-modal-border);
                     border-radius: 4px;
                     padding: 8px 12px;
                     font-size: 14px;
                     font-weight: 500;
                     width: 100%;
                     box-sizing: border-box;
                   " 
                   placeholder="分组名称">
            <div style="font-size: 12px; color: var(--emoji-modal-text); opacity: 0.7;">
              ID: ${group.id}<br>
              表情数:${group.emojis ? group.emojis.length : 0}
            </div>
          </div>
          
          <div style="display: flex; gap: 4px; justify-content: center;">
            <button class="move-up" data-index="${index}" style="
              background: var(--emoji-modal-button-bg);
              border: 1px solid var(--emoji-modal-border);
              border-radius: 3px;
              padding: 6px 12px;
              cursor: pointer;
              font-size: 12px;
              color: var(--emoji-modal-text);
              flex: 1;
            " ${index === 0 ? "disabled" : ""}>↑ 上移</button>
            <button class="move-down" data-index="${index}" style="
              background: var(--emoji-modal-button-bg);
              border: 1px solid var(--emoji-modal-border);
              border-radius: 3px;
              padding: 6px 12px;
              cursor: pointer;
              font-size: 12px;
              color: var(--emoji-modal-text);
              flex: 1;
            " ${index === userscriptState.emojiGroups.length - 1 ? "disabled" : ""}>↓ 下移</button>
          </div>
        </div>
      `).join("")}
    </div>
    
    <div style="margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--emoji-modal-border); display: flex; gap: 8px; justify-content: space-between;">
      <button id="openImportExport" style="padding: 8px 16px; background: var(--emoji-modal-button-bg); color: var(--emoji-modal-text); border: 1px solid var(--emoji-modal-border); border-radius: 4px; cursor: pointer;">分组导入/导出</button>
      <div style="display: flex; gap: 8px;">
        <button id="addNewGroup" style="padding: 8px 16px; background: var(--emoji-modal-primary-bg); color: white; border: none; border-radius: 4px; cursor: pointer;">新建分组</button>
        <button id="saveAllChanges" style="padding: 8px 16px; background: var(--emoji-modal-primary-bg); color: white; border: none; border-radius: 4px; cursor: pointer;">保存所有更改</button>
      </div>
    </div>
  `,
			onClose: () => modal.remove()
		});
		const content = modal.querySelector("div:last-child");
		const modalContent = modal.querySelector("div > div");
		if (modalContent) {
			modalContent.style.width = "80vw";
			modalContent.style.maxWidth = "80vw";
		}
		document.body.appendChild(modal);
		ensureStyleInjected("group-editor-styles", `
    .group-item:hover {
      border-color: var(--emoji-modal-primary-bg) !important;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    .group-icon-editor:hover {
      background: var(--emoji-modal-primary-bg) !important;
      color: white;
    }
    .move-up:hover, .move-down:hover {
      background: var(--emoji-modal-primary-bg) !important;
      color: white;
    }
    .move-up:disabled, .move-down:disabled {
      opacity: 0.3;
      cursor: not-allowed !important;
    }
    .delete-group:hover {
      background: #c82333 !important;
      border-color: #bd2130 !important;
    }
    
    /* Responsive layout adjustments */
    @media (max-width: 1600px) {
      .group-item {
        width: calc(25% - 12px) !important;
      }
    }
    @media (max-width: 1200px) {
      .group-item {
        width: calc(33.333% - 11px) !important;
      }
    }
    @media (max-width: 900px) {
      .group-item {
        width: calc(50% - 8px) !important;
      }
    }
    @media (max-width: 600px) {
      .group-item {
        width: 100% !important;
        min-width: unset !important;
      }
    }
  `);
		content.querySelectorAll(".group-name-editor").forEach((input) => {
			input.addEventListener("change", (e) => {
				const target = e.target;
				const groupId = target.getAttribute("data-group-id");
				const newName = target.value.trim();
				if (groupId && newName) {
					const group = userscriptState.emojiGroups.find((g) => g.id === groupId);
					if (group) {
						group.name = newName;
						showTemporaryMessage(`分组 "${newName}" 名称已更新`);
					}
				}
			});
		});
		content.querySelectorAll(".group-icon-editor").forEach((iconEl) => {
			iconEl.addEventListener("click", (e) => {
				const target = e.target;
				const groupId = target.getAttribute("data-group-id");
				if (groupId) customPrompt("请输入新的图标字符 (emoji 或单个字符):", target.textContent || "📁").then((newIcon) => {
					if (newIcon && newIcon.trim()) {
						const group = userscriptState.emojiGroups.find((g) => g.id === groupId);
						if (group) {
							group.icon = newIcon.trim();
							target.textContent = newIcon.trim();
							showTemporaryMessage(`分组图标已更新为: ${newIcon.trim()}`);
						}
					}
				});
			});
		});
		content.querySelectorAll(".move-up").forEach((btn) => {
			btn.addEventListener("click", (e) => {
				const index = parseInt(e.target.getAttribute("data-index") || "0");
				if (index > 0) {
					const temp = userscriptState.emojiGroups[index];
					userscriptState.emojiGroups[index] = userscriptState.emojiGroups[index - 1];
					userscriptState.emojiGroups[index - 1] = temp;
					modal.remove();
					showTemporaryMessage("分组顺序已调整");
					setTimeout(() => showGroupEditorModal(), 300);
				}
			});
		});
		content.querySelectorAll(".move-down").forEach((btn) => {
			btn.addEventListener("click", (e) => {
				const index = parseInt(e.target.getAttribute("data-index") || "0");
				if (index < userscriptState.emojiGroups.length - 1) {
					const temp = userscriptState.emojiGroups[index];
					userscriptState.emojiGroups[index] = userscriptState.emojiGroups[index + 1];
					userscriptState.emojiGroups[index + 1] = temp;
					modal.remove();
					showTemporaryMessage("分组顺序已调整");
					setTimeout(() => showGroupEditorModal(), 300);
				}
			});
		});
		content.querySelectorAll(".delete-group").forEach((btn) => {
			btn.addEventListener("click", (e) => {
				const target = e.target;
				const index = parseInt(target.getAttribute("data-index") || "0");
				const groupName = target.getAttribute("data-group-name");
				customConfirm$1(`确认删除分组 "${groupName}"?\n\n该分组包含 ${userscriptState.emojiGroups[index].emojis?.length || 0} 个表情。\n删除后数据将无法恢复。`).then((confirmed) => {
					if (confirmed) {
						userscriptState.emojiGroups.splice(index, 1);
						modal.remove();
						showTemporaryMessage(`分组 "${groupName}" 已删除`);
						setTimeout(() => showGroupEditorModal(), 300);
					}
				});
			});
		});
		content.querySelector("#addNewGroup")?.addEventListener("click", () => {
			customPrompt("请输入新分组的名称:").then((groupName) => {
				if (groupName && groupName.trim()) {
					const newGroup = {
						id: "custom_" + Date.now(),
						name: groupName.trim(),
						icon: "📁",
						order: userscriptState.emojiGroups.length,
						emojis: []
					};
					userscriptState.emojiGroups.push(newGroup);
					modal.remove();
					showTemporaryMessage(`新分组 "${groupName.trim()}" 已创建`);
					setTimeout(() => showGroupEditorModal(), 300);
				}
			});
		});
		content.querySelector("#saveAllChanges")?.addEventListener("click", () => {
			saveDataToLocalStorage({ emojiGroups: userscriptState.emojiGroups });
			showTemporaryMessage("所有更改已保存到本地存储");
		});
		content.querySelector("#openImportExport")?.addEventListener("click", () => {
			modal.remove();
			showImportExportModal();
		});
	}
	var init_groupEditor = __esmMin((() => {
		init_state();
		init_userscript_storage();
		init_themeSupport();
		init_tempMessage();
		init_injectStyles();
		init_editorUtils();
		init_importExport();
		init_utils();
	}));
	var manager_exports = /* @__PURE__ */ __export({ openManagementInterface: () => openManagementInterface });
	function createEditorPopup(groupId, index, renderGroups, renderSelectedGroup) {
		const group = userscriptState.emojiGroups.find((g) => g.id === groupId);
		if (!group) return;
		const emo = group.emojis[index];
		if (!emo) return;
		const backdrop = createEl("div", { style: `
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0,0,0,0.5);
    z-index: 1000000;
    display: flex;
    align-items: center;
    justify-content: center;
  ` });
		const editorPanel = createEl("div", { className: "emoji-manager-editor-panel" });
		const editorTitle = createEl("h3", {
			text: "编辑表情",
			className: "emoji-manager-editor-title",
			style: "margin: 0 0 16px 0; text-align: center;"
		});
		const editorPreview = createEl("img", { className: "emoji-manager-editor-preview" });
		editorPreview.src = emo.url;
		const editorWidthInput = createEl("input", {
			className: "form-control",
			placeholder: "宽度 (px) 可选",
			value: emo.width ? String(emo.width) : ""
		});
		const editorHeightInput = createEl("input", {
			className: "form-control",
			placeholder: "高度 (px) 可选",
			value: emo.height ? String(emo.height) : ""
		});
		const editorNameInput = createEl("input", {
			className: "form-control",
			placeholder: "名称 (alias)",
			value: emo.name || ""
		});
		const editorUrlInput = createEl("input", {
			className: "form-control",
			placeholder: "表情图片 URL",
			value: emo.url || ""
		});
		const buttonContainer = createEl("div", { style: "display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px;" });
		const editorSaveBtn = createEl("button", {
			text: "保存修改",
			className: "btn btn-primary"
		});
		const editorCancelBtn = createEl("button", {
			text: "取消",
			className: "btn"
		});
		buttonContainer.appendChild(editorCancelBtn);
		buttonContainer.appendChild(editorSaveBtn);
		editorPanel.appendChild(editorTitle);
		editorPanel.appendChild(editorPreview);
		editorPanel.appendChild(editorWidthInput);
		editorPanel.appendChild(editorHeightInput);
		editorPanel.appendChild(editorNameInput);
		editorPanel.appendChild(editorUrlInput);
		editorPanel.appendChild(buttonContainer);
		backdrop.appendChild(editorPanel);
		document.body.appendChild(backdrop);
		editorUrlInput.addEventListener("input", () => {
			editorPreview.src = editorUrlInput.value;
		});
		editorSaveBtn.addEventListener("click", () => {
			const newName = (editorNameInput.value || "").trim();
			const newUrl = (editorUrlInput.value || "").trim();
			const newWidth = parseInt((editorWidthInput.value || "").trim(), 10);
			const newHeight = parseInt((editorHeightInput.value || "").trim(), 10);
			if (!newName || !newUrl) {
				customAlert("名称和 URL 均不能为空");
				return;
			}
			emo.name = newName;
			emo.url = newUrl;
			if (!isNaN(newWidth) && newWidth > 0) emo.width = newWidth;
			else delete emo.width;
			if (!isNaN(newHeight) && newHeight > 0) emo.height = newHeight;
			else delete emo.height;
			renderGroups();
			renderSelectedGroup();
			backdrop.remove();
		});
		editorCancelBtn.addEventListener("click", () => {
			backdrop.remove();
		});
		backdrop.addEventListener("click", (e) => {
			if (e.target === backdrop) backdrop.remove();
		});
	}
	function openManagementInterface() {
		injectManagerStyles();
		const modal = createEl("div", {
			className: "emoji-manager-wrapper",
			attrs: {
				role: "dialog",
				"aria-modal": "true"
			}
		});
		const panel = createEl("div", { className: "emoji-manager-panel" });
		const left = createEl("div", { className: "emoji-manager-left" });
		const leftHeader = createEl("div", { className: "emoji-manager-left-header" });
		const title = createEl("h3", { text: "表情管理器" });
		const closeBtn = createEl("button", {
			text: "×",
			className: "btn",
			style: "font-size:20px; background:none; border:none; cursor:pointer;"
		});
		leftHeader.appendChild(title);
		leftHeader.appendChild(closeBtn);
		left.appendChild(leftHeader);
		const addGroupRow = createEl("div", { className: "emoji-manager-addgroup-row" });
		const addGroupInput = createEl("input", {
			placeholder: "新分组 id",
			className: "form-control"
		});
		const addGroupBtn = createEl("button", {
			text: "添加",
			className: "btn"
		});
		addGroupRow.appendChild(addGroupInput);
		addGroupRow.appendChild(addGroupBtn);
		left.appendChild(addGroupRow);
		const groupSelectorContainer = createEl("div", { className: "emoji-manager-group-selector" });
		const groupSelector = createEl("select", {
			className: "form-control",
			attrs: { "aria-label": "选择表情分组" }
		});
		groupSelectorContainer.appendChild(groupSelector);
		left.appendChild(groupSelectorContainer);
		const groupsList = createEl("div", { className: "emoji-manager-groups-list" });
		left.appendChild(groupsList);
		const right = createEl("div", { className: "emoji-manager-right" });
		const rightHeader = createEl("div", { className: "emoji-manager-right-header" });
		const groupTitle = createEl("h4");
		groupTitle.textContent = "";
		const deleteGroupBtn = createEl("button", {
			text: "删除分组",
			className: "btn",
			style: "background:#ef4444; color:#fff;"
		});
		rightHeader.appendChild(groupTitle);
		rightHeader.appendChild(deleteGroupBtn);
		right.appendChild(rightHeader);
		const managerRightMain = createEl("div", { className: "emoji-manager-right-main" });
		const emojisContainer = createEl("div", { className: "emoji-manager-emojis" });
		managerRightMain.appendChild(emojisContainer);
		const addEmojiForm = createEl("div", { className: "emoji-manager-add-emoji-form" });
		const emojiUrlInput = createEl("input", {
			placeholder: "表情图片 URL",
			className: "form-control"
		});
		const emojiNameInput = createEl("input", {
			placeholder: "名称 (alias)",
			className: "form-control"
		});
		const emojiWidthInput = createEl("input", {
			placeholder: "宽度 (px) 可选",
			className: "form-control"
		});
		const emojiHeightInput = createEl("input", {
			placeholder: "高度 (px) 可选",
			className: "form-control"
		});
		const addEmojiBtn = createEl("button", {
			text: "添加表情",
			className: "btn btn-primary",
			attrs: {
				"data-action": "add-emoji",
				"aria-label": "添加表情到当前分组"
			}
		});
		addEmojiForm.appendChild(emojiUrlInput);
		addEmojiForm.appendChild(emojiNameInput);
		addEmojiForm.appendChild(emojiWidthInput);
		addEmojiForm.appendChild(emojiHeightInput);
		addEmojiForm.appendChild(addEmojiBtn);
		managerRightMain.appendChild(addEmojiForm);
		right.appendChild(managerRightMain);
		const footer = createEl("div", { className: "emoji-manager-footer" });
		const exportBtn = createEl("button", {
			text: "分组导出",
			className: "btn"
		});
		const importBtn = createEl("button", {
			text: "分组导入",
			className: "btn"
		});
		const groupEditBtn = createEl("button", {
			text: "分组编辑",
			className: "btn",
			style: "background:#3b82f6; color:#fff;"
		});
		const restoreBtn = createEl("button", {
			text: "恢复默认配置",
			className: "btn",
			style: "background:#f97316; color:#fff;"
		});
		const exitBtn = createEl("button", {
			text: "退出",
			className: "btn"
		});
		exitBtn.addEventListener("click", () => modal.remove());
		const saveBtn = createEl("button", {
			text: "保存",
			className: "btn btn-primary"
		});
		const syncBtn = createEl("button", {
			text: "同步管理器",
			className: "btn"
		});
		groupEditBtn.addEventListener("click", () => {
			modal.remove();
			showGroupEditorModal();
		});
		restoreBtn.addEventListener("click", async () => {
			if (!await customConfirm("确认恢复到默认配置?此操作将清除当前所有分组和表情,且不可撤销!")) return;
			try {
				const defaultGroups = await loadAndFilterDefaultEmojiGroups(void 0, window.location.hostname);
				if (!defaultGroups || defaultGroups.length === 0) {
					await customAlert("无法加载默认配置,请检查网络连接");
					return;
				}
				userscriptState.emojiGroups = defaultGroups;
				saveDataToLocalStorage({ emojiGroups: userscriptState.emojiGroups });
				renderGroups();
				renderSelectedGroup();
				await customAlert("已成功恢复到默认配置");
			} catch (error) {
				console.error("Failed to restore default configuration:", error);
				await customAlert("恢复默认配置失败:" + error);
			}
		});
		footer.appendChild(groupEditBtn);
		footer.appendChild(restoreBtn);
		footer.appendChild(syncBtn);
		footer.appendChild(exportBtn);
		footer.appendChild(importBtn);
		footer.appendChild(exitBtn);
		footer.appendChild(saveBtn);
		panel.appendChild(left);
		panel.appendChild(right);
		panel.appendChild(footer);
		modal.appendChild(panel);
		document.body.appendChild(modal);
		let selectedGroupId = null;
		function renderGroups() {
			groupsList.innerHTML = "";
			groupSelector.innerHTML = "";
			if (!selectedGroupId && userscriptState.emojiGroups.length > 0) selectedGroupId = userscriptState.emojiGroups[0].id;
			userscriptState.emojiGroups.forEach((g) => {
				const row = createEl("div", {
					style: "display:flex; justify-content:space-between; align-items:center; padding:6px; border-radius:4px; cursor:pointer;",
					text: `${g.name || g.id} (${(g.emojis || []).length})`,
					attrs: {
						tabindex: "0",
						"data-group-id": g.id
					}
				});
				const selectGroup = () => {
					selectedGroupId = g.id;
					renderGroups();
					renderSelectedGroup();
				};
				row.addEventListener("click", selectGroup);
				row.addEventListener("keydown", (e) => {
					if (e.key === "Enter" || e.key === " ") {
						e.preventDefault();
						selectGroup();
					}
				});
				if (selectedGroupId === g.id) row.style.background = "#f0f8ff";
				groupsList.appendChild(row);
				const option = createEl("option", {
					text: `${g.name || g.id} (${(g.emojis || []).length})`,
					attrs: { value: g.id }
				});
				if (selectedGroupId === g.id) option.selected = true;
				groupSelector.appendChild(option);
			});
		}
		function showEditorFor(groupId, index) {
			createEditorPopup(groupId, index, renderGroups, renderSelectedGroup);
		}
		function renderSelectedGroup() {
			const group = userscriptState.emojiGroups.find((g) => g.id === selectedGroupId) || null;
			groupTitle.textContent = group ? group.name || group.id : "";
			emojisContainer.innerHTML = "";
			if (!group) return;
			(Array.isArray(group.emojis) ? group.emojis : []).forEach((emo, idx) => {
				const card = createEl("div", { className: "emoji-manager-card" });
				const img = createEl("img", {
					src: emo.url,
					alt: emo.name,
					className: "emoji-manager-card-img"
				});
				const name = createEl("div", {
					text: emo.name,
					className: "emoji-manager-card-name"
				});
				const actions = createEl("div", { className: "emoji-manager-card-actions" });
				const edit = createEl("button", {
					text: "编辑",
					className: "btn btn-sm",
					attrs: {
						"data-action": "edit-emoji",
						"aria-label": `编辑表情 ${emo.name}`
					}
				});
				edit.addEventListener("click", () => {
					showEditorFor(group.id, idx);
				});
				const del = createEl("button", {
					text: "删除",
					className: "btn btn-sm",
					attrs: {
						"data-action": "delete-emoji",
						"aria-label": `删除表情 ${emo.name}`
					}
				});
				del.addEventListener("click", () => {
					group.emojis.splice(idx, 1);
					renderGroups();
					renderSelectedGroup();
				});
				emojiManagerConfig.injectionPoints.addButton(actions, edit);
				emojiManagerConfig.injectionPoints.addButton(actions, del);
				card.appendChild(img);
				card.appendChild(name);
				card.appendChild(actions);
				emojiManagerConfig.injectionPoints.insertCard(emojisContainer, card);
				bindHoverPreview(img, emo);
			});
		}
		function bindHoverPreview(targetImg, emo) {
			const preview = ensureHoverPreview();
			const previewImg = preview.querySelector("img");
			const previewLabel = preview.querySelector(".emoji-picker-hover-label");
			function onEnter(e) {
				if (previewImg) previewImg.src = emo.url;
				if (previewImg) {
					if (emo.width) previewImg.style.width = typeof emo.width === "number" ? emo.width + "px" : emo.width;
					else previewImg.style.width = "";
					if (emo.height) previewImg.style.height = typeof emo.height === "number" ? emo.height + "px" : emo.height;
					else previewImg.style.height = "";
				}
				if (previewLabel) previewLabel.textContent = emo.name || "";
				preview.style.display = "block";
				movePreview(e);
			}
			function movePreview(e) {
				const pad = 12;
				const vw = window.innerWidth;
				const vh = window.innerHeight;
				const rect = preview.getBoundingClientRect();
				let left$1 = e.clientX + pad;
				let top = e.clientY + pad;
				if (left$1 + rect.width > vw) left$1 = e.clientX - rect.width - pad;
				if (top + rect.height > vh) top = e.clientY - rect.height - pad;
				preview.style.left = left$1 + "px";
				preview.style.top = top + "px";
			}
			function onLeave() {
				preview.style.display = "none";
			}
			targetImg.addEventListener("mouseenter", onEnter);
			targetImg.addEventListener("mousemove", movePreview);
			targetImg.addEventListener("mouseleave", onLeave);
		}
		addGroupBtn.addEventListener("click", async () => {
			const id = (addGroupInput.value || "").trim();
			if (!id) {
				await customAlert("请输入分组 id");
				return;
			}
			if (userscriptState.emojiGroups.find((g) => g.id === id)) {
				await customAlert("分组已存在");
				return;
			}
			userscriptState.emojiGroups.push({
				id,
				name: id,
				emojis: []
			});
			addGroupInput.value = "";
			const newIdx = userscriptState.emojiGroups.findIndex((g) => g.id === id);
			if (newIdx >= 0) selectedGroupId = userscriptState.emojiGroups[newIdx].id;
			renderGroups();
			renderSelectedGroup();
		});
		groupSelector.addEventListener("change", () => {
			selectedGroupId = groupSelector.value;
			renderGroups();
			renderSelectedGroup();
		});
		addEmojiBtn.addEventListener("click", async () => {
			if (!selectedGroupId) {
				await customAlert("请先选择分组");
				return;
			}
			const url = emojiManagerConfig.parsers.getUrl({ urlInput: emojiUrlInput });
			const name = emojiManagerConfig.parsers.getName({
				nameInput: emojiNameInput,
				urlInput: emojiUrlInput
			});
			const width = emojiManagerConfig.parsers.getWidth({ widthInput: emojiWidthInput });
			const height = emojiManagerConfig.parsers.getHeight({ heightInput: emojiHeightInput });
			if (!url || !name) {
				await customAlert("请输入 url 和 名称");
				return;
			}
			const group = userscriptState.emojiGroups.find((g) => g.id === selectedGroupId);
			if (!group) return;
			group.emojis = group.emojis || [];
			const newEmo = {
				url,
				name
			};
			if (width !== void 0) newEmo.width = width;
			if (height !== void 0) newEmo.height = height;
			group.emojis.push(newEmo);
			emojiUrlInput.value = "";
			emojiNameInput.value = "";
			emojiWidthInput.value = "";
			emojiHeightInput.value = "";
			renderGroups();
			renderSelectedGroup();
		});
		deleteGroupBtn.addEventListener("click", async () => {
			if (!selectedGroupId) {
				await customAlert("请先选择分组");
				return;
			}
			const idx = userscriptState.emojiGroups.findIndex((g) => g.id === selectedGroupId);
			if (idx >= 0) {
				if (!await customConfirm("确认删除该分组?该操作不可撤销")) return;
				userscriptState.emojiGroups.splice(idx, 1);
				if (userscriptState.emojiGroups.length > 0) selectedGroupId = userscriptState.emojiGroups[Math.min(idx, userscriptState.emojiGroups.length - 1)].id;
				else selectedGroupId = null;
				renderGroups();
				renderSelectedGroup();
			}
		});
		exportBtn.addEventListener("click", () => {
			showImportExportModal(selectedGroupId || void 0);
		});
		importBtn.addEventListener("click", () => {
			showImportExportModal(selectedGroupId || void 0);
		});
		saveBtn.addEventListener("click", async () => {
			try {
				saveDataToLocalStorage({ emojiGroups: userscriptState.emojiGroups });
				await customAlert("已保存");
			} catch (e) {
				await customAlert("保存失败:" + e);
			}
		});
		syncBtn.addEventListener("click", async () => {
			try {
				if (syncFromManager()) {
					const data = loadDataFromLocalStorage();
					userscriptState.emojiGroups = data.emojiGroups || [];
					userscriptState.settings = data.settings || userscriptState.settings;
					await customAlert("同步成功,已导入管理器数据");
					renderGroups();
					renderSelectedGroup();
				} else await customAlert("同步未成功,未检测到管理器数据");
			} catch (e) {
				await customAlert("同步异常:" + e);
			}
		});
		closeBtn.addEventListener("click", () => modal.remove());
		modal.addEventListener("click", (e) => {
			if (e.target === modal) modal.remove();
		});
		renderGroups();
		if (userscriptState.emojiGroups.length > 0) {
			selectedGroupId = userscriptState.emojiGroups[0].id;
			const first = groupsList.firstChild;
			if (first) first.style.background = "#f0f8ff";
			renderSelectedGroup();
		}
	}
	var emojiManagerConfig;
	var init_manager = __esmMin((() => {
		init_styles();
		init_createEl();
		init_state();
		init_hoverPreview();
		init_userscript_storage();
		init_importExport();
		init_default_emoji_loader();
		init_groupEditor();
		init_utils();
		emojiManagerConfig = {
			selectors: {
				container: ".emoji-manager-emojis",
				card: ".emoji-manager-card",
				actionRow: ".emoji-manager-card-actions",
				editButton: ".btn.btn-sm:first-child",
				deleteButton: ".btn.btn-sm:last-child"
			},
			parsers: {
				getUrl: ({ urlInput }) => (urlInput.value || "").trim(),
				getName: ({ nameInput, urlInput }) => {
					const name = (nameInput.value || "").trim();
					if (!name && urlInput.value) return (urlInput.value.trim().split("/").pop() || "").replace(/\.[^.]+$/, "") || "表情";
					return name || "表情";
				},
				getWidth: ({ widthInput }) => {
					const val = (widthInput.value || "").trim();
					const parsed = parseInt(val, 10);
					return !isNaN(parsed) && parsed > 0 ? parsed : void 0;
				},
				getHeight: ({ heightInput }) => {
					const val = (heightInput.value || "").trim();
					const parsed = parseInt(val, 10);
					return !isNaN(parsed) && parsed > 0 ? parsed : void 0;
				}
			},
			injectionPoints: {
				addButton: (parent, button) => {
					parent.appendChild(button);
				},
				insertCard: (container, card) => {
					container.appendChild(card);
				}
			}
		};
	}));
	function showPopularEmojisModal() {
		injectGlobalThemeStyles();
		const popularEmojis = getPopularEmojis(50);
		const contentHTML = `
    <div style="margin-bottom: 16px; padding: 12px; background: var(--emoji-modal-button-bg); border-radius: 6px; border: 1px solid var(--emoji-modal-border);">
      <div style="display: flex; justify-content: space-between; align-items: center;">
        <span style="font-weight: 500; color: var(--emoji-modal-label);">表情按使用次数排序</span>
        <span style="font-size: 12px; color: var(--emoji-modal-text);">点击表情直接使用</span>
      </div>
      <div style="font-size: 12px; color: var(--emoji-modal-text);">
        总使用次数:${popularEmojis.reduce((sum, emoji) => sum + emoji.count, 0)}
      </div>
    </div>
    
    <div id="popularEmojiGrid" style="
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
      gap: 8px;
      max-height: 400px;
      overflow-y: auto;
    ">
      ${popularEmojis.length === 0 ? "<div style=\"grid-column: 1/-1; text-align: center; padding: 40px; color: var(--emoji-modal-text);\">还没有使用过表情<br><small>开始使用表情后,这里会显示常用的表情</small></div>" : popularEmojis.map((emoji) => `
          <div class="popular-emoji-item" data-name="${emoji.name}" data-url="${emoji.url}" style="
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 8px;
            border: 1px solid var(--emoji-modal-border);
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.2s;
            background: var(--emoji-modal-button-bg);
          ">
            <img src="${emoji.url}" alt="${emoji.name}" style="
              width: 40px;
              height: 40px;
              object-fit: contain;
              margin-bottom: 4px;
            ">
            <div style="
              font-size: 11px;
              font-weight: 500;
              color: var(--emoji-modal-text);
              text-align: center;
              word-break: break-all;
              line-height: 1.2;
              margin-bottom: 2px;
            ">${emoji.name}</div>
            <div style="
              font-size: 10px;
              color: var(--emoji-modal-text);
              opacity: 0.6;
              text-align: center;
            ">使用${emoji.count}次</div>
          </div>
        `).join("")}
    </div>
    
    ${popularEmojis.length > 0 ? `
      <div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--emoji-modal-border); font-size: 12px; color: var(--emoji-modal-text); opacity: 0.6; text-align: center;">
        统计数据保存在本地,清空统计将重置所有使用记录
      </div>
    ` : ""}
  `;
		const modal = createModalElement({
			title: `常用表情 (${popularEmojis.length})`,
			content: contentHTML,
			onClose: () => modal.remove()
		});
		const titleDiv = modal.querySelector("div:first-child > div:first-child, div:first-child > h2 + div");
		if (titleDiv) {
			const clearStatsButton = createEl("button", {
				id: "clearStats",
				text: "清空统计",
				style: "padding: 6px 12px; background: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; margin-right: 8px;"
			});
			titleDiv.appendChild(clearStatsButton);
			clearStatsButton.addEventListener("click", () => {
				if (confirm("确定要清空所有表情使用统计吗?此操作不可撤销。")) {
					clearEmojiUsageStats();
					modal.remove();
					showTemporaryMessage("表情使用统计已清空");
					setTimeout(() => showPopularEmojisModal(), 300);
				}
			});
		}
		const content = modal.querySelector("div:last-child");
		document.body.appendChild(modal);
		ensureStyleInjected("popular-emojis-styles", `
    .popular-emoji-item:hover {
      transform: translateY(-2px);
      border-color: var(--emoji-modal-primary-bg) !important;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }
  `);
		content.querySelectorAll(".popular-emoji-item").forEach((item) => {
			item.addEventListener("click", () => {
				const name = item.getAttribute("data-name");
				const url = item.getAttribute("data-url");
				if (name && url) {
					trackEmojiUsage(name, url);
					useEmojiFromPopular(name, url);
					modal.remove();
					showTemporaryMessage(`已使用表情: ${name}`);
				}
			});
		});
	}
	function useEmojiFromPopular(name, url) {
		const activeElement = document.activeElement;
		if (activeElement && (activeElement.tagName === "TEXTAREA" || activeElement.tagName === "INPUT")) {
			const textArea = activeElement;
			const format = userscriptState.settings.outputFormat;
			let emojiText = "";
			if (format === "markdown") emojiText = `![${name}](${url})`;
			else emojiText = `<img src="${url}" alt="${name}" style="width: ${userscriptState.settings.imageScale}px; height: ${userscriptState.settings.imageScale}px;">`;
			const start = textArea.selectionStart || 0;
			const end = textArea.selectionEnd || 0;
			const currentValue = textArea.value;
			textArea.value = currentValue.slice(0, start) + emojiText + currentValue.slice(end);
			const newPosition = start + emojiText.length;
			textArea.setSelectionRange(newPosition, newPosition);
			textArea.dispatchEvent(new Event("input", { bubbles: true }));
			textArea.focus();
		} else {
			const textAreas = document.querySelectorAll("textarea, input[type=\"text\"], [contenteditable=\"true\"]");
			const lastTextArea = Array.from(textAreas).pop();
			if (lastTextArea) {
				lastTextArea.focus();
				if (lastTextArea.tagName === "TEXTAREA" || lastTextArea.tagName === "INPUT") {
					const format = userscriptState.settings.outputFormat;
					let emojiText = "";
					if (format === "markdown") emojiText = `![${name}](${url})`;
					else emojiText = `<img src="${url}" alt="${name}" style="width: ${userscriptState.settings.imageScale}px; height: ${userscriptState.settings.imageScale}px;">`;
					const textarea = lastTextArea;
					textarea.value += emojiText;
					textarea.dispatchEvent(new Event("input", { bubbles: true }));
				}
			}
		}
	}
	var init_popularEmojis = __esmMin((() => {
		init_state();
		init_userscript_storage();
		init_createEl();
		init_themeSupport();
		init_tempMessage();
		init_injectStyles();
		init_editorUtils();
	}));
	var settings_exports = /* @__PURE__ */ __export({ showSettingsModal: () => showSettingsModal });
	function showSettingsModal() {
		injectGlobalThemeStyles();
		const modal = createEl("div", { style: `
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 999999;
    display: flex;
    align-items: center;
    justify-content: center;
  ` });
		modal.appendChild(createEl("div", {
			style: `
    backdrop-filter: blur(10px);
    padding: 24px;
    overflow-y: auto;
    position: relative;
  `,
			innerHTML: `
    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
      <h2 style="margin: 0; color: var(--emoji-modal-text);">设置</h2>
      <button id="closeModal" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #999;">×</button>
    </div>
    
    <div style="margin-bottom: 16px;">
      <label style="display: block; margin-bottom: 8px; color: var(--emoji-modal-label); font-weight: 500;">图片缩放比例:<span id="scaleValue">${userscriptState.settings.imageScale}%</span></label>
      <input type="range" id="scaleSlider" min="5" max="150" step="5" value="${userscriptState.settings.imageScale}" 
             style="width: 100%; margin-bottom: 8px;">
    </div>
    
    <div style="margin-bottom: 16px;">
      <label style="display: block; margin-bottom: 8px; color: var(--emoji-modal-label); font-weight: 500;">输出格式:</label>
      <div style="display: flex; gap: 16px;">
        <label style="display: flex; align-items: center; color: var(--emoji-modal-text);">
          <input type="radio" name="outputFormat" value="markdown" ${userscriptState.settings.outputFormat === "markdown" ? "checked" : ""} style="margin-right: 4px;">
          Markdown
        </label>
        <label style="display: flex; align-items: center; color: var(--emoji-modal-text);">
          <input type="radio" name="outputFormat" value="html" ${userscriptState.settings.outputFormat === "html" ? "checked" : ""} style="margin-right: 4px;">
          HTML
        </label>
      </div>
    </div>
    
    <div style="margin-bottom: 16px;">
      <label style="display: flex; align-items: center; color: var(--emoji-modal-label); font-weight: 500;">
        <input type="checkbox" id="showSearchBar" ${userscriptState.settings.showSearchBar ? "checked" : ""} style="margin-right: 8px;">
        显示搜索栏
      </label>
    </div>
    
    <div style="margin-bottom: 16px;">
      <label style="display: flex; align-items: center; color: var(--emoji-modal-label); font-weight: 500;">
        <input type="checkbox" id="enableFloatingPreview" ${userscriptState.settings.enableFloatingPreview ? "checked" : ""} style="margin-right: 8px;">
        启用悬浮预览功能
      </label>
    </div>

    <div style="margin-bottom: 16px;">
      <label style="display: flex; align-items: center; color: var(--emoji-modal-label); font-weight: 500;">
        <input type="checkbox" id="enableCalloutSuggestions" ${userscriptState.settings.enableCalloutSuggestions ? "checked" : ""} style="margin-right: 8px;">
        在 textarea 中启用 Callout Suggestion(输入 [ 即可触发)
      </label>
    </div>

    <div style="margin-bottom: 16px;">
      <label style="display: flex; align-items: center; color: var(--emoji-modal-label); font-weight: 500;">
        <input type="checkbox" id="enableBatchParseImages" ${userscriptState.settings.enableBatchParseImages ? "checked" : ""} style="margin-right: 8px;">
        注入“一键解析并添加所有图片”按钮
      </label>
    </div>
    
    <div style="margin-bottom: 16px;">
      <label style="display: flex; align-items: center; color: var(--emoji-modal-label); font-weight: 500;">
        <input type="checkbox" id="forceMobileMode" ${userscriptState.settings.forceMobileMode ? "checked" : ""} style="margin-right: 8px;">
        强制移动模式 (在不兼容检测时也注入移动版布局)
      </label>
    </div>
    
    <div style="margin-bottom: 16px; padding: 12px; background: var(--emoji-modal-button-bg); border-radius: 6px; border: 1px solid var(--emoji-modal-border);">
      <div style="font-weight: 500; color: var(--emoji-modal-label); margin-bottom: 8px;">高级功能</div>
      <div style="display: flex; gap: 8px; flex-wrap: wrap;">
        <button id="openGroupEditor" style="
          padding: 6px 12px; 
          background: var(--emoji-modal-primary-bg); 
          color: white; 
          border: none; 
          font-size: 12px;
        ">编辑分组</button>
        <button id="openPopularEmojis" style="
          padding: 6px 12px; 
          background: var(--emoji-modal-primary-bg); 
          color: white; 
          border: none; 
          font-size: 12px;
        ">常用表情</button>
        <button id="openImportExport" style="
          padding: 6px 12px; 
          background: var(--emoji-modal-primary-bg); 
          color: white; 
          border: none; 
          font-size: 12px;
        ">导入导出</button>
      </div>
    </div>
    
    <div style="display: flex; gap: 8px; justify-content: flex-end;">
      <button id="resetSettings" style="padding: 8px 16px; background: var(--emoji-modal-button-bg); color: var(--emoji-modal-text); border: 1px solid var(--emoji-modal-border); border-radius: 4px; cursor: pointer;">重置</button>
      <button id="saveSettings" style="padding: 8px 16px; background: var(--emoji-modal-primary-bg); color: white; border: none; border-radius: 4px; cursor: pointer;">保存</button>
    </div>
  `
		}));
		document.body.appendChild(modal);
		const content = modal.querySelector("div:last-child");
		const scaleSlider = content.querySelector("#scaleSlider");
		const scaleValue = content.querySelector("#scaleValue");
		content.querySelector("#closeModal")?.addEventListener("click", () => {
			modal.remove();
		});
		scaleSlider?.addEventListener("input", () => {
			if (scaleValue) scaleValue.textContent = scaleSlider.value + "%";
		});
		content.querySelector("#resetSettings")?.addEventListener("click", async () => {
			if (confirm("确定要重置所有设置吗?")) {
				userscriptState.settings = { ...DEFAULT_USER_SETTINGS };
				modal.remove();
			}
		});
		content.querySelector("#saveSettings")?.addEventListener("click", () => {
			userscriptState.settings.imageScale = parseInt(scaleSlider?.value || "30");
			const outputFormat = content.querySelector("input[name=\"outputFormat\"]:checked");
			if (outputFormat) userscriptState.settings.outputFormat = outputFormat.value;
			const showSearchBar = content.querySelector("#showSearchBar");
			if (showSearchBar) userscriptState.settings.showSearchBar = showSearchBar.checked;
			const enableFloatingPreview = content.querySelector("#enableFloatingPreview");
			if (enableFloatingPreview) userscriptState.settings.enableFloatingPreview = enableFloatingPreview.checked;
			const enableCalloutEl = content.querySelector("#enableCalloutSuggestions");
			if (enableCalloutEl) userscriptState.settings.enableCalloutSuggestions = !!enableCalloutEl.checked;
			const enableBatchEl = content.querySelector("#enableBatchParseImages");
			if (enableBatchEl) userscriptState.settings.enableBatchParseImages = !!enableBatchEl.checked;
			const forceMobileEl = content.querySelector("#forceMobileMode");
			if (forceMobileEl) userscriptState.settings.forceMobileMode = !!forceMobileEl.checked;
			saveDataToLocalStorage({ settings: userscriptState.settings });
			try {
				const remoteInput = content.querySelector("#remoteConfigUrl");
				if (remoteInput && remoteInput.value.trim()) localStorage.setItem("emoji_extension_remote_config_url", remoteInput.value.trim());
			} catch (e) {}
			alert("设置已保存");
			modal.remove();
		});
		content.querySelector("#openGroupEditor")?.addEventListener("click", () => {
			modal.remove();
			showGroupEditorModal();
		});
		content.querySelector("#openPopularEmojis")?.addEventListener("click", () => {
			modal.remove();
			showPopularEmojisModal();
		});
		content.querySelector("#openImportExport")?.addEventListener("click", () => {
			modal.remove();
			showImportExportModal();
		});
	}
	var init_settings = __esmMin((() => {
		init_state();
		init_userscript_storage();
		init_createEl();
		init_themeSupport();
		init_groupEditor();
		init_popularEmojis();
		init_importExport();
	}));
	function createSyncTarget(config) {
		switch (config.type) {
			case "webdav": return new WebDAVSyncTarget(config);
			case "s3": return new S3SyncTarget(config);
			case "cloudflare": return new CloudflareSyncTarget(config);
			default: throw new Error(`Unknown sync target type: ${config.type}`);
		}
	}
	var WebDAVSyncTarget, S3SyncTarget, CloudflareSyncTarget;
	var init_syncTargets = __esmMin((() => {
		WebDAVSyncTarget = class {
			config;
			constructor(config) {
				this.config = config;
			}
			getAuthHeader() {
				return `Basic ${btoa(`${this.config.username}:${this.config.password}`)}`;
			}
			getFullUrl() {
				return `${this.config.url.replace(/\/$/, "")}/${this.config.path || "emoji-data.json"}`;
			}
			async test() {
				try {
					const url = this.getFullUrl();
					const response = await fetch(url, {
						method: "HEAD",
						headers: { Authorization: this.getAuthHeader() }
					});
					if (response.ok || response.status === 404) return {
						success: true,
						message: "WebDAV connection successful",
						timestamp: Date.now()
					};
					return {
						success: false,
						message: `WebDAV connection failed: ${response.statusText}`,
						error: response.statusText
					};
				} catch (error) {
					return {
						success: false,
						message: `WebDAV connection error: ${error}`,
						error
					};
				}
			}
			async push(data, onProgress) {
				try {
					onProgress?.({
						current: 0,
						total: 1,
						action: "push"
					});
					const url = this.getFullUrl();
					const response = await fetch(url, {
						method: "PUT",
						headers: {
							Authorization: this.getAuthHeader(),
							"Content-Type": "application/json"
						},
						body: JSON.stringify(data, null, 2)
					});
					onProgress?.({
						current: 1,
						total: 1,
						action: "push"
					});
					if (response.ok || response.status === 201 || response.status === 204) return {
						success: true,
						message: "Data pushed to WebDAV successfully",
						timestamp: Date.now()
					};
					return {
						success: false,
						message: `Failed to push to WebDAV: ${response.statusText}`,
						error: response.statusText
					};
				} catch (error) {
					return {
						success: false,
						message: `Error pushing to WebDAV: ${error}`,
						error
					};
				}
			}
			async pull(onProgress) {
				try {
					onProgress?.({
						current: 0,
						total: 1,
						action: "pull"
					});
					const url = this.getFullUrl();
					const response = await fetch(url, {
						method: "GET",
						headers: {
							Authorization: this.getAuthHeader(),
							Accept: "application/json"
						}
					});
					onProgress?.({
						current: 1,
						total: 1,
						action: "pull"
					});
					if (response.ok) return {
						success: true,
						data: await response.json(),
						message: "Data pulled from WebDAV successfully"
					};
					if (response.status === 404) return {
						success: false,
						message: "No data found on WebDAV server",
						error: "File not found"
					};
					return {
						success: false,
						message: `Failed to pull from WebDAV: ${response.statusText}`,
						error: response.statusText
					};
				} catch (error) {
					return {
						success: false,
						message: `Error pulling from WebDAV: ${error}`,
						error
					};
				}
			}
		};
		S3SyncTarget = class {
			config;
			constructor(config) {
				this.config = config;
			}
			getObjectKey() {
				const path = this.config.path || "emoji-data.json";
				return path.startsWith("/") ? path.substring(1) : path;
			}
			async signRequest(method, url, body) {
				const date = (/* @__PURE__ */ new Date()).toISOString().replace(/[:-]|\.\d{3}/g, "");
				date.substring(0, 8);
				const headers = {
					"x-amz-date": date,
					"x-amz-content-sha256": "UNSIGNED-PAYLOAD"
				};
				if (body) headers["Content-Type"] = "application/json";
				return headers;
			}
			getS3Url() {
				const endpoint = this.config.endpoint.replace(/\/$/, "");
				const bucket = this.config.bucket;
				const key = this.getObjectKey();
				if (endpoint.endsWith(".amazonaws.com") || endpoint === "s3.amazonaws.com") return `https://${bucket}.${endpoint}/${key}`;
				return `${endpoint}/${bucket}/${key}`;
			}
			async test() {
				try {
					const url = this.getS3Url();
					const headers = await this.signRequest("HEAD", url);
					const response = await fetch(url, {
						method: "HEAD",
						headers
					});
					if (response.ok || response.status === 404) return {
						success: true,
						message: "S3 connection successful",
						timestamp: Date.now()
					};
					return {
						success: false,
						message: `S3 connection failed: ${response.statusText}`,
						error: response.statusText
					};
				} catch (error) {
					return {
						success: false,
						message: `S3 connection error: ${error}`,
						error
					};
				}
			}
			async push(data, onProgress) {
				try {
					onProgress?.({
						current: 0,
						total: 1,
						action: "push"
					});
					const url = this.getS3Url();
					const body = JSON.stringify(data, null, 2);
					const headers = await this.signRequest("PUT", url, body);
					const response = await fetch(url, {
						method: "PUT",
						headers,
						body
					});
					onProgress?.({
						current: 1,
						total: 1,
						action: "push"
					});
					if (response.ok || response.status === 201 || response.status === 204) return {
						success: true,
						message: "Data pushed to S3 successfully",
						timestamp: Date.now()
					};
					return {
						success: false,
						message: `Failed to push to S3: ${response.statusText}`,
						error: response.statusText
					};
				} catch (error) {
					return {
						success: false,
						message: `Error pushing to S3: ${error}`,
						error
					};
				}
			}
			async pull(onProgress) {
				try {
					onProgress?.({
						current: 0,
						total: 1,
						action: "pull"
					});
					const url = this.getS3Url();
					const headers = await this.signRequest("GET", url);
					const response = await fetch(url, {
						method: "GET",
						headers
					});
					onProgress?.({
						current: 1,
						total: 1,
						action: "pull"
					});
					if (response.ok) return {
						success: true,
						data: await response.json(),
						message: "Data pulled from S3 successfully"
					};
					if (response.status === 404) return {
						success: false,
						message: "No data found on S3",
						error: "Object not found"
					};
					return {
						success: false,
						message: `Failed to pull from S3: ${response.statusText}`,
						error: response.statusText
					};
				} catch (error) {
					return {
						success: false,
						message: `Error pulling from S3: ${error}`,
						error
					};
				}
			}
		};
		CloudflareSyncTarget = class {
			config;
			constructor(config) {
				this.config = config;
			}
			getWriteAuthHeader() {
				return { Authorization: `Bearer ${this.config.authToken}` };
			}
			getReadAuthHeader() {
				return { Authorization: `Bearer ${this.config.authTokenReadonly || this.config.authToken}` };
			}
			getUrl() {
				return this.config.url.replace(/\/$/, "");
			}
			async test() {
				try {
					const url = this.getUrl() + "/";
					const response = await fetch(url, {
						method: "GET",
						headers: this.getReadAuthHeader()
					});
					if (response.ok) {
						await response.json();
						return {
							success: true,
							message: "Cloudflare Worker connection successful",
							timestamp: Date.now()
						};
					}
					return {
						success: false,
						message: `Cloudflare Worker connection failed: ${response.statusText}`,
						error: response.statusText
					};
				} catch (error) {
					return {
						success: false,
						message: `Cloudflare Worker connection error: ${error}`,
						error
					};
				}
			}
			async push(data, onProgress) {
				try {
					const baseUrl = this.getUrl();
					const headers = {
						...this.getWriteAuthHeader(),
						"Content-Type": "application/json"
					};
					const itemsToPush = [{
						key: "settings",
						data: data.settings
					}, ...data.emojiGroups.map((g) => ({
						key: encodeURIComponent(g.name),
						data: g
					}))];
					const total = itemsToPush.length;
					let current = 0;
					onProgress?.({
						current,
						total,
						action: "push"
					});
					for (const item of itemsToPush) {
						const response = await fetch(`${baseUrl}/${item.key}`, {
							method: "POST",
							headers,
							body: JSON.stringify(item.data)
						});
						if (!response.ok) throw new Error(`Failed to push item ${item.key}: ${response.statusText}`);
						current++;
						onProgress?.({
							current,
							total,
							action: "push"
						});
					}
					return {
						success: true,
						message: `Data pushed to Cloudflare Worker successfully (${total} items).`,
						timestamp: Date.now()
					};
				} catch (error) {
					return {
						success: false,
						message: `Error pushing to Cloudflare Worker: ${error}`,
						error
					};
				}
			}
			async pull(onProgress) {
				try {
					const baseUrl = this.getUrl();
					const headers = this.getReadAuthHeader();
					let current = 0;
					onProgress?.({
						current,
						total: 1,
						action: "pull"
					});
					const listResponse = await fetch(`${baseUrl}/`, {
						method: "GET",
						headers
					});
					if (!listResponse.ok) throw new Error(`Failed to list keys: ${listResponse.statusText}`);
					const keys = await listResponse.json();
					const total = keys.length;
					onProgress?.({
						current,
						total,
						action: "pull"
					});
					const pulledItems = [];
					for (const key of keys) {
						const res = await fetch(`${baseUrl}/${key.name}`, {
							method: "GET",
							headers
						});
						if (!res.ok) {
							console.warn(`Failed to fetch key ${key.name}, skipping.`);
							continue;
						}
						const data = await res.json();
						pulledItems.push({
							key: key.name,
							data
						});
						current++;
						onProgress?.({
							current,
							total,
							action: "pull"
						});
					}
					const pulledData = { emojiGroups: [] };
					let version = "0.0.0";
					let timestamp = Date.now();
					for (const item of pulledItems) if (item.key === "settings") pulledData.settings = item.data;
					else pulledData.emojiGroups.push(item.data);
					if (pulledData.settings?.version) version = pulledData.settings.version;
					if (pulledData.settings?.timestamp) timestamp = pulledData.settings.timestamp;
					return {
						success: true,
						data: {
							settings: pulledData.settings || {},
							emojiGroups: pulledData.emojiGroups || [],
							version,
							timestamp
						},
						message: `Data pulled from Cloudflare Worker successfully (${pulledItems.length} items).`
					};
				} catch (error) {
					console.error("Error pulling from Cloudflare Worker:", error);
					return {
						success: false,
						message: `Error pulling from Cloudflare Worker: ${error}`,
						error
					};
				}
			}
		};
	}));
	var syncManager_exports = /* @__PURE__ */ __export({
		showSyncConfigModal: () => showSyncConfigModal,
		showSyncOperationsModal: () => showSyncOperationsModal
	});
	function loadSyncConfig() {
		try {
			const configData = localStorage.getItem(SYNC_CONFIG_KEY);
			if (configData) return JSON.parse(configData);
		} catch (error) {
			console.error("[Sync Manager] Failed to load sync config:", error);
		}
		return null;
	}
	function saveSyncConfig(config) {
		try {
			localStorage.setItem(SYNC_CONFIG_KEY, JSON.stringify(config));
			console.log("[Sync Manager] Sync config saved");
		} catch (error) {
			console.error("[Sync Manager] Failed to save sync config:", error);
		}
	}
	function createSyncDataFromState() {
		return {
			emojiGroups: userscriptState.emojiGroups,
			settings: userscriptState.settings,
			timestamp: Date.now(),
			version: "1.0"
		};
	}
	function applySyncDataToState(data) {
		const mergedData = mergeSyncData(createSyncDataFromState(), data);
		userscriptState.emojiGroups = mergedData.emojiGroups;
		userscriptState.settings = mergedData.settings;
		saveDataToLocalStorage({
			emojiGroups: userscriptState.emojiGroups,
			settings: userscriptState.settings
		});
	}
	function mergeSyncData(local, remote) {
		const mergedSettings = {
			...local.settings,
			...remote.settings
		};
		const localGroupsMap = new Map(local.emojiGroups.map((g) => [g.name, g]));
		const mergedGroups = [...local.emojiGroups];
		for (const remoteGroup of remote.emojiGroups) {
			const localGroup = localGroupsMap.get(remoteGroup.name);
			if (localGroup) {
				const localEmojisMap = new Map((localGroup.emojis || []).map((e) => [e.name, e]));
				for (const remoteEmoji of remoteGroup.emojis || []) localEmojisMap.set(remoteEmoji.name, remoteEmoji);
				localGroup.emojis = Array.from(localEmojisMap.values());
			} else mergedGroups.push(remoteGroup);
		}
		return {
			settings: mergedSettings,
			emojiGroups: mergedGroups,
			timestamp: remote.timestamp,
			version: remote.version
		};
	}
	function showSyncConfigModal() {
		const existingConfig = loadSyncConfig();
		const syncType = existingConfig?.type || "webdav";
		const modal = createModalElement({
			title: "同步配置",
			content: `
    <div style="margin-bottom: 16px; padding: 12px; background: #fef3c7; border: 1px solid #f59e0b; border-radius: 4px; color: #92400e;">
      <div style="font-weight: bold; margin-bottom: 4px;">⚠️ 安全提示</div>
      <div style="font-size: 13px;">
        您的密码和密钥将以明文形式存储在浏览器的 localStorage 中。请确保:
        <ul style="margin: 8px 0 0 0; padding-left: 20px;">
          <li>仅在受信任的设备上使用此功能</li>
          <li>使用强密码和独立的凭据</li>
          <li>定期更换密码和密钥</li>
        </ul>
      </div>
    </div>

    <div style="margin-bottom: 24px;">
      <h3 style="margin: 0 0 12px 0; color: var(--emoji-modal-label);">同步类型</h3>
      <select id="syncTypeSelect" style="
        width: 100%;
        padding: 8px;
        background: var(--emoji-modal-button-bg);
        color: var(--emoji-modal-text);
        border: 1px solid var(--emoji-modal-border);
        border-radius: 4px;
        margin-bottom: 16px;
      ">
        <option value="webdav" ${syncType === "webdav" ? "selected" : ""}>WebDAV</option>
        <option value="s3" ${syncType === "s3" ? "selected" : ""}>S3</option>
        <option value="cloudflare" ${syncType === "cloudflare" ? "selected" : ""}>Cloudflare Worker</option>
      </select>
    </div>

    <!-- Cloudflare Worker Configuration -->
    <div id="cloudflareConfig" style="display: ${syncType === "cloudflare" ? "block" : "none"};">
      <h3 style="margin: 0 0 12px 0; color: var(--emoji-modal-label);">Cloudflare Worker 配置</h3>
      
      <div style="margin-bottom: 12px;">
        <label style="display: block; margin-bottom: 4px; color: var(--emoji-modal-label);">Worker URL:</label>
        <input type="text" id="cfWorkerUrl" placeholder="https://your-worker.workers.dev" value="${existingConfig?.type === "cloudflare" ? existingConfig.url : ""}" style="
          width: 100%;
          padding: 8px;
          background: var(--emoji-modal-button-bg);
          color: var(--emoji-modal-text);
          border: 1px solid var(--emoji-modal-border);
          border-radius: 4px;
        ">
      </div>

      <div style="margin-bottom: 12px;">
        <label style="display: block; margin-bottom: 4px; color: var(--emoji-modal-label);">读写授权密钥 (Auth Token):</label>
        <input type="password" id="cfAuthToken" value="${existingConfig?.type === "cloudflare" ? existingConfig.authToken : ""}" style="
          width: 100%;
          padding: 8px;
          background: var(--emoji-modal-button-bg);
          color: var(--emoji-modal-text);
          border: 1px solid var(--emoji-modal-border);
          border-radius: 4px;
        ">
      </div>

      <div style="margin-bottom: 12px;">
        <label style="display: block; margin-bottom: 4px; color: var(--emoji-modal-label);">只读授权密钥 (可选):</label>
        <input type="password" id="cfAuthTokenReadonly" value="${existingConfig?.type === "cloudflare" ? existingConfig.authTokenReadonly || "" : ""}" style="
          width: 100%;
          padding: 8px;
          background: var(--emoji-modal-button-bg);
          color: var(--emoji-modal-text);
          border: 1px solid var(--emoji-modal-border);
          border-radius: 4px;
        ">
        <div style="font-size: 12px; color: var(--emoji-modal-text); opacity: 0.7; margin-top: 4px;">
          如果提供,拉取数据时将优先使用此密钥。
        </div>
      </div>
    </div>

    <!-- WebDAV Configuration -->
    <div id="webdavConfig" style="display: ${syncType === "webdav" ? "block" : "none"};">
      <h3 style="margin: 0 0 12px 0; color: var(--emoji-modal-label);">WebDAV 配置</h3>
      <input type="text" id="webdavUrl" placeholder="服务器 URL" value="${existingConfig?.type === "webdav" ? existingConfig.url : ""}">
      <input type="text" id="webdavUsername" placeholder="用户名" value="${existingConfig?.type === "webdav" ? existingConfig.username : ""}">
      <input type="password" id="webdavPassword" placeholder="密码" value="${existingConfig?.type === "webdav" ? existingConfig.password : ""}">
      <input type="text" id="webdavPath" placeholder="文件路径 (可选)" value="${existingConfig?.type === "webdav" ? existingConfig.path || "" : ""}">
    </div>

    <!-- S3 Configuration -->
    <div id="s3Config" style="display: ${syncType === "s3" ? "block" : "none"};">
      <h3 style="margin: 0 0 12px 0; color: var(--emoji-modal-label);">S3 配置</h3>
      <input type="text" id="s3Endpoint" placeholder="Endpoint" value="${existingConfig?.type === "s3" ? existingConfig.endpoint : ""}">
      <input type="text" id="s3Region" placeholder="Region" value="${existingConfig?.type === "s3" ? existingConfig.region : ""}">
      <input type="text" id="s3Bucket" placeholder="Bucket" value="${existingConfig?.type === "s3" ? existingConfig.bucket : ""}">
      <input type="text" id="s3AccessKeyId" placeholder="Access Key ID" value="${existingConfig?.type === "s3" ? existingConfig.accessKeyId : ""}">
      <input type="password" id="s3SecretAccessKey" placeholder="Secret Access Key" value="${existingConfig?.type === "s3" ? existingConfig.secretAccessKey : ""}">
      <input type="text" id="s3Path" placeholder="路径前缀 (可选)" value="${existingConfig?.type === "s3" ? existingConfig.path || "" : ""}">
    </div>

    <div style="display: flex; gap: 8px; margin-top: 16px;">
      <button id="testConnection">测试连接</button>
      <button id="saveConfig">保存配置</button>
    </div>
  `,
			onClose: () => modal.remove()
		});
		document.body.appendChild(modal);
		const syncTypeSelect = modal.querySelector("#syncTypeSelect");
		const webdavConfigDiv = modal.querySelector("#webdavConfig");
		const s3ConfigDiv = modal.querySelector("#s3Config");
		const cloudflareConfigDiv = modal.querySelector("#cloudflareConfig");
		syncTypeSelect.addEventListener("change", () => {
			const selectedType = syncTypeSelect.value;
			webdavConfigDiv.style.display = selectedType === "webdav" ? "block" : "none";
			s3ConfigDiv.style.display = selectedType === "s3" ? "block" : "none";
			cloudflareConfigDiv.style.display = selectedType === "cloudflare" ? "block" : "none";
		});
		modal.querySelector("#testConnection")?.addEventListener("click", async () => {
			const config = getCurrentConfigFromModal(modal);
			if (!config) {
				showTemporaryMessage("请填写完整的配置信息", "error");
				return;
			}
			const btn = modal.querySelector("#testConnection");
			btn.disabled = true;
			btn.textContent = "测试中...";
			try {
				const result = await createSyncTarget(config).test();
				showTemporaryMessage(result.message, result.success ? "success" : "error");
			} catch (error) {
				showTemporaryMessage(`测试失败: ${error}`, "error");
			} finally {
				btn.disabled = false;
				btn.textContent = "测试连接";
			}
		});
		modal.querySelector("#saveConfig")?.addEventListener("click", () => {
			const config = getCurrentConfigFromModal(modal);
			if (!config) {
				showTemporaryMessage("请填写完整的配置信息", "error");
				return;
			}
			saveSyncConfig(config);
			showTemporaryMessage("配置已保存", "success");
			modal.remove();
		});
	}
	function getCurrentConfigFromModal(modal) {
		const syncType = modal.querySelector("#syncTypeSelect").value;
		if (syncType === "cloudflare") {
			const url = modal.querySelector("#cfWorkerUrl").value.trim();
			const authToken = modal.querySelector("#cfAuthToken").value.trim();
			const authTokenReadonly = modal.querySelector("#cfAuthTokenReadonly").value.trim();
			if (!url || !authToken) return null;
			return {
				type: "cloudflare",
				enabled: true,
				url,
				authToken,
				authTokenReadonly: authTokenReadonly || void 0
			};
		}
		if (syncType === "webdav") {
			const url = modal.querySelector("#webdavUrl").value.trim();
			const username = modal.querySelector("#webdavUsername").value.trim();
			const password = modal.querySelector("#webdavPassword").value.trim();
			const path = modal.querySelector("#webdavPath").value.trim();
			if (!url || !username || !password) return null;
			return {
				type: "webdav",
				enabled: true,
				url,
				username,
				password,
				path: path || void 0
			};
		} else if (syncType === "s3") {
			const endpoint = modal.querySelector("#s3Endpoint").value.trim();
			const region = modal.querySelector("#s3Region").value.trim();
			const bucket = modal.querySelector("#s3Bucket").value.trim();
			const accessKeyId = modal.querySelector("#s3AccessKeyId").value.trim();
			const secretAccessKey = modal.querySelector("#s3SecretAccessKey").value.trim();
			const path = modal.querySelector("#s3Path").value.trim();
			if (!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) return null;
			return {
				type: "s3",
				enabled: true,
				endpoint,
				region,
				bucket,
				accessKeyId,
				secretAccessKey,
				path: path || void 0
			};
		}
		return null;
	}
	function showPullPreviewModal(data, config, onConfirm) {
		const groupListHTML = data.emojiGroups.length > 0 ? `<ul>${data.emojiGroups.map((g) => `<li style="color: var(--emoji-modal-text);">${g.name}</li>`).join("")}</ul>` : "<p style=\"color: var(--emoji-modal-text);\">没有表情分组</p>";
		const modal = createModalElement({
			title: "确认合并恢复",
			content: `
    <div style="margin-bottom: 16px;">
      <h3 style="margin: 0 0 12px 0; color: var(--emoji-modal-label);">恢复预览</h3>
      <div style="padding: 12px; background: var(--emoji-modal-button-bg); border-radius: 4px;">
        <div style="color: var(--emoji-modal-text); margin-bottom: 8px;">
          <strong>备份时间:</strong> ${new Date(data.timestamp).toLocaleString()}
        </div>
        <div style="color: var(--emoji-modal-text); margin-bottom: 8px;">
          <strong>表情分组数量:</strong> ${data.emojiGroups.length}
        </div>
        <div>
          <strong style="color: var(--emoji-modal-text);">分组列表:</strong>
          <div style="max-height: 150px; overflow-y: auto; border: 1px solid var(--emoji-modal-border); padding: 8px; border-radius: 4px; margin-top: 4px;">
            ${groupListHTML}
          </div>
        </div>
      </div>
    </div>
    <p style="color: #f59e0b; font-weight: bold;">将使用此备份与本地数据合并。此操作不可撤销。</p>
    <div style="display: flex; gap: 8px; margin-top: 16px; justify-content: flex-end;">
      <button id="cancelPull">取消</button>
      <button id="confirmPull">确认合并</button>
    </div>
  `,
			onClose: () => modal.remove()
		});
		document.body.appendChild(modal);
		modal.querySelector("#confirmPull")?.addEventListener("click", () => {
			applySyncDataToState(data);
			config.lastSyncTime = Date.now();
			saveSyncConfig(config);
			showTemporaryMessage("数据合并成功,页面将刷新", "success");
			modal.remove();
			onConfirm();
			setTimeout(() => {
				window.location.reload();
			}, 1e3);
		});
		modal.querySelector("#cancelPull")?.addEventListener("click", () => {
			modal.remove();
		});
	}
	function showSyncOperationsModal() {
		const config = loadSyncConfig();
		if (!config) {
			showTemporaryMessage("请先配置同步设置", "error");
			showSyncConfigModal();
			return;
		}
		const lastSyncTime = config.lastSyncTime ? new Date(config.lastSyncTime).toLocaleString() : "从未同步";
		const modal = createModalElement({
			title: "同步管理",
			content: `
    <div style="margin-bottom: 24px;">
      <h3 style="margin: 0 0 12px 0; color: var(--emoji-modal-label);">同步状态</h3>
      <div style="padding: 12px; background: var(--emoji-modal-button-bg); border-radius: 4px; margin-bottom: 16px;">
        <div style="color: var(--emoji-modal-text); margin-bottom: 4px;">
          <strong>同步类型:</strong> ${config.type.toUpperCase()}
        </div>
        <div style="color: var(--emoji-modal-text);">
          <strong>最后同步:</strong> ${lastSyncTime}
        </div>
      </div>
    </div>

    <!-- Progress Indicator -->
    <div id="syncProgressContainer" style="display: none; margin-bottom: 16px;">
      <div id="syncProgressText" style="margin-bottom: 4px; color: var(--emoji-modal-text);"></div>
      <progress id="syncProgressBar" value="0" max="100" style="width: 100%;"></progress>
    </div>

    <div style="margin-bottom: 24px;">
      <h3 style="margin: 0 0 12px 0; color: var(--emoji-modal-label);">同步操作</h3>
      
      <div style="margin-bottom: 16px;">
        <button id="pushData">⬆️ 推送 (Push) - 上传本地数据到服务器</button>
        <div style="font-size: 12px; color: var(--emoji-modal-text); opacity: 0.7; margin-top: 4px;">
          将当前的表情分组和设置推送到远程服务器
        </div>
      </div>

      <div style="margin-bottom: 16px;">
        <button id="pullData">⬇️ 拉取 (Pull) - 从服务器合并数据</button>
        <div style="font-size: 12px; color: var(--emoji-modal-text); opacity: 0.7; margin-top: 4px;">
          从远程服务器拉取数据并与本地数据合并
        </div>
      </div>

      <div>
        <button id="configSync">⚙️ 同步配置</button>
      </div>
    </div>
  `,
			onClose: () => modal.remove()
		});
		document.body.appendChild(modal);
		const progressContainer = modal.querySelector("#syncProgressContainer");
		const progressText = modal.querySelector("#syncProgressText");
		const progressBar = modal.querySelector("#syncProgressBar");
		const pullBtn = modal.querySelector("#pullData");
		const pushBtn = modal.querySelector("#pushData");
		const updateProgress = (progress) => {
			progressContainer.style.display = "block";
			progressText.textContent = `${progress.action === "push" ? "推送" : "拉取"}中... (${progress.current} / ${progress.total})`;
			progressBar.max = progress.total;
			progressBar.value = progress.current;
		};
		const hideProgress = () => {
			progressContainer.style.display = "none";
		};
		pushBtn.addEventListener("click", async () => {
			pushBtn.disabled = true;
			pullBtn.disabled = true;
			pushBtn.textContent = "推送中...";
			updateProgress({
				current: 0,
				total: 1,
				action: "push"
			});
			try {
				const target = createSyncTarget(config);
				const syncData = createSyncDataFromState();
				const result = await target.push(syncData, updateProgress);
				if (result.success) {
					config.lastSyncTime = Date.now();
					saveSyncConfig(config);
					showTemporaryMessage("数据推送成功", "success");
					modal.remove();
				} else showTemporaryMessage(`推送失败: ${result.message}`, "error");
			} catch (error) {
				showTemporaryMessage(`推送错误: ${error}`, "error");
			} finally {
				pushBtn.disabled = false;
				pullBtn.disabled = false;
				pushBtn.textContent = "⬆️ 推送 (Push) - 上传本地数据到服务器";
				hideProgress();
			}
		});
		pullBtn.addEventListener("click", async () => {
			pullBtn.disabled = true;
			pushBtn.disabled = true;
			pullBtn.textContent = "拉取中...";
			updateProgress({
				current: 0,
				total: 1,
				action: "pull"
			});
			try {
				const result = await createSyncTarget(config).pull(updateProgress);
				if (result.success && result.data) showPullPreviewModal(result.data, config, () => modal.remove());
				else showTemporaryMessage(`拉取失败: ${result.message}`, "error");
			} catch (error) {
				showTemporaryMessage(`拉取错误: ${error}`, "error");
			} finally {
				pullBtn.disabled = false;
				pushBtn.disabled = false;
				pullBtn.textContent = "⬇️ 拉取 (Pull) - 从服务器合并数据";
				hideProgress();
			}
		});
		modal.querySelector("#configSync")?.addEventListener("click", () => {
			modal.remove();
			showSyncConfigModal();
		});
	}
	var SYNC_CONFIG_KEY;
	var init_syncManager = __esmMin((() => {
		init_state();
		init_userscript_storage();
		init_editorUtils();
		init_tempMessage();
		init_syncTargets();
		SYNC_CONFIG_KEY = "emoji_extension_sync_config";
	}));
	init_userscript_storage();
	init_state();
	init_platformDetection();
	async function initializeUserscriptData() {
		const data = await loadDataFromLocalStorageAsync(window.location.hostname).catch((err) => {
			console.warn("[Manager] loadDataFromLocalStorageAsync failed, falling back to sync loader", err);
			return loadDataFromLocalStorage();
		});
		userscriptState.emojiGroups = data.emojiGroups || [];
		userscriptState.settings = data.settings || userscriptState.settings;
	}
	function isDiscoursePage() {
		if (document.querySelectorAll("meta[name*=\"discourse\"], meta[content*=\"discourse\"], meta[property*=\"discourse\"]").length > 0) {
			console.log("[Emoji Manager] Discourse detected via meta tags");
			return true;
		}
		const generatorMeta = document.querySelector("meta[name=\"generator\"]");
		if (generatorMeta) {
			if ((generatorMeta.getAttribute("content")?.toLowerCase() || "").includes("discourse")) {
				console.log("[Emoji Manager] Discourse detected via generator meta");
				return true;
			}
		}
		if (document.querySelectorAll("#main-outlet, .ember-application, textarea.d-editor-input, .ProseMirror.d-editor-input").length > 0) {
			console.log("[Emoji Manager] Discourse elements detected");
			return true;
		}
		console.log("[Emoji Manager] Not a Discourse site");
		return false;
	}
	async function initializeEmojiManager() {
		console.log("[Emoji Manager] Initializing...");
		logPlatformInfo();
		await initializeUserscriptData();
		const isMobileQuery = window.matchMedia("(max-width: 768px)");
		const isMobileUserAgent = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase());
		const isMobile = isMobileQuery.matches || isMobileUserAgent;
		const managerButton = document.createElement("button");
		managerButton.id = "emoji-manager-floating-button";
		managerButton.textContent = isMobile ? "⚙️" : "⚙️ 表情管理";
		managerButton.title = "Open Emoji Management Interface";
		Object.assign(managerButton.style, {
			position: "fixed",
			right: isMobile ? "16px" : "12px",
			bottom: isMobile ? "16px" : "12px",
			zIndex: "2147483647",
			padding: isMobile ? "14px 18px" : "12px 16px",
			borderRadius: isMobile ? "12px" : "8px",
			border: "none",
			background: "#1f2937",
			color: "#fff",
			fontSize: isMobile ? "16px" : "14px",
			fontWeight: "500",
			boxShadow: "0 6px 18px rgba(0,0,0,0.3)",
			cursor: "pointer",
			transition: "transform 0.2s",
			minWidth: isMobile ? "56px" : "auto",
			minHeight: isMobile ? "56px" : "auto"
		});
		managerButton.addEventListener("mouseenter", () => {
			if (!isMobile) managerButton.style.transform = "scale(1.05)";
		});
		managerButton.addEventListener("mouseleave", () => {
			if (!isMobile) managerButton.style.transform = "scale(1)";
		});
		managerButton.addEventListener("touchstart", () => {
			managerButton.style.transform = "scale(0.95)";
		});
		managerButton.addEventListener("touchend", () => {
			managerButton.style.transform = "scale(1)";
		});
		managerButton.addEventListener("click", async () => {
			try {
				const { openManagementInterface: openManagementInterface$1 } = await __vitePreload(async () => {
					const { openManagementInterface: openManagementInterface$2 } = await Promise.resolve().then(() => (init_manager(), manager_exports));
					return { openManagementInterface: openManagementInterface$2 };
				}, void 0);
				openManagementInterface$1();
			} catch (e) {
				console.error("[Emoji Manager] Failed to open management interface:", e);
			}
		});
		const settingsButton = document.createElement("button");
		settingsButton.id = "emoji-settings-floating-button";
		settingsButton.textContent = isMobile ? "🔧" : "🔧 设置";
		settingsButton.title = "Open Settings";
		Object.assign(settingsButton.style, {
			position: "fixed",
			right: isMobile ? "16px" : "12px",
			bottom: isMobile ? "84px" : "70px",
			zIndex: "2147483647",
			padding: isMobile ? "12px 16px" : "10px 14px",
			borderRadius: isMobile ? "12px" : "8px",
			border: "none",
			background: "#374151",
			color: "#fff",
			fontSize: isMobile ? "15px" : "13px",
			fontWeight: "500",
			boxShadow: "0 4px 12px rgba(0,0,0,0.2)",
			cursor: "pointer",
			transition: "transform 0.2s",
			minWidth: isMobile ? "52px" : "auto",
			minHeight: isMobile ? "52px" : "auto"
		});
		settingsButton.addEventListener("mouseenter", () => {
			if (!isMobile) settingsButton.style.transform = "scale(1.05)";
		});
		settingsButton.addEventListener("mouseleave", () => {
			if (!isMobile) settingsButton.style.transform = "scale(1)";
		});
		settingsButton.addEventListener("touchstart", () => {
			settingsButton.style.transform = "scale(0.95)";
		});
		settingsButton.addEventListener("touchend", () => {
			settingsButton.style.transform = "scale(1)";
		});
		settingsButton.addEventListener("click", async () => {
			try {
				const { showSettingsModal: showSettingsModal$1 } = await __vitePreload(async () => {
					const { showSettingsModal: showSettingsModal$2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
					return { showSettingsModal: showSettingsModal$2 };
				}, void 0);
				showSettingsModal$1();
			} catch (e) {
				console.error("[Emoji Manager] Failed to open settings:", e);
			}
		});
		const importExportButton = document.createElement("button");
		importExportButton.id = "emoji-importexport-floating-button";
		importExportButton.textContent = isMobile ? "📦" : "📦 导入/导出";
		importExportButton.title = "Import/Export Data";
		Object.assign(importExportButton.style, {
			position: "fixed",
			right: isMobile ? "16px" : "12px",
			bottom: isMobile ? "152px" : "128px",
			zIndex: "2147483647",
			padding: isMobile ? "12px 16px" : "10px 14px",
			borderRadius: isMobile ? "12px" : "8px",
			border: "none",
			background: "#374151",
			color: "#fff",
			fontSize: isMobile ? "15px" : "13px",
			fontWeight: "500",
			boxShadow: "0 4px 12px rgba(0,0,0,0.2)",
			cursor: "pointer",
			transition: "transform 0.2s",
			minWidth: isMobile ? "52px" : "auto",
			minHeight: isMobile ? "52px" : "auto"
		});
		importExportButton.addEventListener("mouseenter", () => {
			if (!isMobile) importExportButton.style.transform = "scale(1.05)";
		});
		importExportButton.addEventListener("mouseleave", () => {
			if (!isMobile) importExportButton.style.transform = "scale(1)";
		});
		importExportButton.addEventListener("touchstart", () => {
			importExportButton.style.transform = "scale(0.95)";
		});
		importExportButton.addEventListener("touchend", () => {
			importExportButton.style.transform = "scale(1)";
		});
		importExportButton.addEventListener("click", async () => {
			try {
				const { showImportExportModal: showImportExportModal$1 } = await __vitePreload(async () => {
					const { showImportExportModal: showImportExportModal$2 } = await Promise.resolve().then(() => (init_importExport(), importExport_exports));
					return { showImportExportModal: showImportExportModal$2 };
				}, void 0);
				showImportExportModal$1();
			} catch (e) {
				console.error("[Emoji Manager] Failed to open import/export:", e);
			}
		});
		const syncButton = document.createElement("button");
		syncButton.id = "emoji-sync-floating-button";
		syncButton.textContent = isMobile ? "☁️" : "☁️ 同步";
		syncButton.title = "Sync with WebDAV/S3";
		Object.assign(syncButton.style, {
			position: "fixed",
			right: isMobile ? "16px" : "12px",
			bottom: isMobile ? "220px" : "186px",
			zIndex: "2147483647",
			padding: isMobile ? "12px 16px" : "10px 14px",
			borderRadius: isMobile ? "12px" : "8px",
			border: "none",
			background: "#374151",
			color: "#fff",
			fontSize: isMobile ? "15px" : "13px",
			fontWeight: "500",
			boxShadow: "0 4px 12px rgba(0,0,0,0.2)",
			cursor: "pointer",
			transition: "transform 0.2s",
			minWidth: isMobile ? "52px" : "auto",
			minHeight: isMobile ? "52px" : "auto"
		});
		syncButton.addEventListener("mouseenter", () => {
			if (!isMobile) syncButton.style.transform = "scale(1.05)";
		});
		syncButton.addEventListener("mouseleave", () => {
			if (!isMobile) syncButton.style.transform = "scale(1)";
		});
		syncButton.addEventListener("touchstart", () => {
			syncButton.style.transform = "scale(0.95)";
		});
		syncButton.addEventListener("touchend", () => {
			syncButton.style.transform = "scale(1)";
		});
		syncButton.addEventListener("click", async () => {
			try {
				const { showSyncOperationsModal: showSyncOperationsModal$1 } = await __vitePreload(async () => {
					const { showSyncOperationsModal: showSyncOperationsModal$2 } = await Promise.resolve().then(() => (init_syncManager(), syncManager_exports));
					return { showSyncOperationsModal: showSyncOperationsModal$2 };
				}, void 0);
				showSyncOperationsModal$1();
			} catch (e) {
				console.error("[Emoji Manager] Failed to open sync manager:", e);
			}
		});
		if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", () => {
			document.body.appendChild(managerButton);
			document.body.appendChild(settingsButton);
			document.body.appendChild(importExportButton);
			document.body.appendChild(syncButton);
		});
		else {
			document.body.appendChild(managerButton);
			document.body.appendChild(settingsButton);
			document.body.appendChild(importExportButton);
			document.body.appendChild(syncButton);
		}
		console.log("[Emoji Manager] Initialization complete");
	}
	if (isDiscoursePage()) {
		console.log("[Emoji Manager] Discourse detected, initializing management interface");
		initializeEmojiManager();
	} else console.log("[Emoji Manager] Not a Discourse site, skipping initialization");
})();

})();