Linux do 表情扩展 (Emoji Extension) lite

为论坛网站添加表情选择器功能 (Add emoji picker functionality to forum websites)

// ==UserScript==
// @name         Linux do 表情扩展 (Emoji Extension) lite
// @namespace    https://github.com/stevessr/bug-v3
// @version      1.0.6
// @description  为论坛网站添加表情选择器功能 (Add emoji picker functionality to forum websites)
// @author       stevessr
// @match        https://linux.do/*
// @match        https://meta.discourse.org/*
// @match        https://*.discourse.org/*
// @match        http://localhost:5173/*
// @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() {
		try {
			if (typeof fetch === "undefined") return null;
			const res = await fetch("/assets/defaultEmojiGroups.json", { cache: "no-cache" });
			if (!res.ok) return null;
			return await res.json();
		} catch (err) {
			return null;
		}
	}
	async function loadDefaultEmojiGroups() {
		const packaged = await fetchPackagedJSON();
		if (packaged && Array.isArray(packaged.groups)) return packaged.groups;
		return [];
	}
	var init_defaultEmojiGroups_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 = {
				imageScale: 30,
				gridColumns: 4,
				outputFormat: "markdown",
				forceMobileMode: false,
				defaultGroup: "nachoneko",
				showSearchBar: true,
				enableFloatingPreview: true
			};
			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);
			console.error("[Userscript] Failed to load from localStorage:", error);
			return {
				emojiGroups: [],
				settings: {
					imageScale: 30,
					gridColumns: 4,
					outputFormat: "markdown",
					forceMobileMode: false,
					defaultGroup: "nachoneko",
					showSearchBar: true,
					enableFloatingPreview: true
				}
			};
		}
	}
	async function loadDataFromLocalStorageAsync() {
		try {
			const local = loadDataFromLocalStorage();
			if (local.emojiGroups && local.emojiGroups.length > 0) return local;
			const remoteUrl = localStorage.getItem("emoji_extension_remote_config_url");
			if (remoteUrl && typeof remoteUrl === "string" && remoteUrl.trim().length > 0) try {
				const controller = new AbortController();
				const timeout = setTimeout(() => controller.abort(), 5e3);
				const res = await fetch(remoteUrl, { signal: controller.signal });
				clearTimeout(timeout);
				if (res && res.ok) {
					const json = await res.json();
					const groups = Array.isArray(json.emojiGroups) ? json.emojiGroups : Array.isArray(json.groups) ? json.groups : null;
					const settings = json.settings && typeof json.settings === "object" ? json.settings : local.settings;
					if (groups && groups.length > 0) {
						try {
							localStorage.setItem(STORAGE_KEY, JSON.stringify(groups));
						} catch (e) {
							console.warn("[Userscript] Failed to persist fetched remote groups to localStorage", e);
						}
						return {
							emojiGroups: groups.filter((g) => g.id !== "favorites"),
							settings
						};
					}
				}
			} catch (err) {
				console.warn("[Userscript] Failed to fetch remote default config:", err);
			}
			try {
				const runtime = await loadDefaultEmojiGroups();
				const source = runtime && runtime.length ? runtime : [];
				const filtered = JSON.parse(JSON.stringify(source)).filter((g) => g.id !== "favorites");
				try {
					localStorage.setItem(STORAGE_KEY, JSON.stringify(filtered));
				} catch (e) {}
				return {
					emojiGroups: filtered,
					settings: local.settings
				};
			} catch (e) {
				console.error("[Userscript] Failed to load default groups in async fallback:", e);
				return {
					emojiGroups: [],
					settings: local.settings
				};
			}
		} catch (error) {
			console.error("[Userscript] loadDataFromLocalStorageAsync failed:", error);
			return {
				emojiGroups: [],
				settings: {
					imageScale: 30,
					gridColumns: 4,
					outputFormat: "markdown",
					forceMobileMode: false,
					defaultGroup: "nachoneko",
					showSearchBar: true,
					enableFloatingPreview: true
				}
			};
		}
	}
	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 addEmojiToUserscript(emojiData) {
		try {
			const data = loadDataFromLocalStorage();
			let userGroup = data.emojiGroups.find((g) => g.id === "user_added");
			if (!userGroup) {
				userGroup = {
					id: "user_added",
					name: "用户添加",
					icon: "⭐",
					order: 999,
					emojis: []
				};
				data.emojiGroups.push(userGroup);
			}
			if (!userGroup.emojis.some((e) => e.url === emojiData.url || e.name === emojiData.name)) {
				userGroup.emojis.push({
					packet: Date.now(),
					name: emojiData.name,
					url: emojiData.url
				});
				saveDataToLocalStorage({ emojiGroups: data.emojiGroups });
				console.log("[Userscript] Added emoji to user group:", emojiData.name);
			} else console.log("[Userscript] Emoji already exists:", emojiData.name);
		} catch (error) {
			console.error("[Userscript] Failed to add emoji:", error);
		}
	}
	function exportUserscriptData() {
		try {
			const data = loadDataFromLocalStorage();
			return JSON.stringify(data, null, 2);
		} catch (error) {
			console.error("[Userscript] Failed to export data:", error);
			return "";
		}
	}
	function importUserscriptData(jsonData) {
		try {
			const data = JSON.parse(jsonData);
			if (data.emojiGroups && Array.isArray(data.emojiGroups)) saveDataToLocalStorage({ emojiGroups: data.emojiGroups });
			if (data.settings && typeof data.settings === "object") saveDataToLocalStorage({ settings: data.settings });
			console.log("[Userscript] Data imported successfully");
			return true;
		} catch (error) {
			console.error("[Userscript] Failed to import data:", error);
			return false;
		}
	}
	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;
	var init_userscript_storage = __esmMin((() => {
		init_defaultEmojiGroups_loader();
		STORAGE_KEY = "emoji_extension_userscript_data";
		SETTINGS_KEY = "emoji_extension_userscript_settings";
		USAGE_STATS_KEY = "emoji_extension_userscript_usage_stats";
	}));
	var userscriptState;
	var init_state = __esmMin((() => {
		userscriptState = {
			emojiGroups: [],
			settings: {
				imageScale: 30,
				gridColumns: 4,
				outputFormat: "markdown",
				forceMobileMode: false,
				defaultGroup: "nachoneko",
				showSearchBar: true,
				enableFloatingPreview: true
			},
			emojiUsageStats: {}
		};
	}));
	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;
		}
		return el;
	}
	var init_createEl = __esmMin((() => {}));
	init_createEl();
	init_state();
	init_userscript_storage();
	function notify(message, type = "info", timeout = 4e3) {
		try {
			let container = document.getElementById("emoji-ext-toast-container");
			if (!container) {
				container = document.createElement("div");
				container.id = "emoji-ext-toast-container";
				container.style.position = "fixed";
				container.style.right = "12px";
				container.style.bottom = "12px";
				container.style.zIndex = "2147483647";
				container.style.display = "flex";
				container.style.flexDirection = "column";
				container.style.gap = "8px";
				document.body.appendChild(container);
			}
			const el = document.createElement("div");
			el.textContent = message;
			el.style.padding = "8px 12px";
			el.style.borderRadius = "6px";
			el.style.boxShadow = "0 2px 8px rgba(0,0,0,0.12)";
			el.style.color = "#ffffff";
			el.style.fontSize = "13px";
			el.style.maxWidth = "320px";
			el.style.wordBreak = "break-word";
			if (type === "success") el.style.background = "#16a34a";
			else if (type === "error") el.style.background = "#dc2626";
			else el.style.background = "#0369a1";
			container.appendChild(el);
			const id = setTimeout(() => {
				el.remove();
				clearTimeout(id);
			}, timeout);
			return () => {
				el.remove();
				clearTimeout(id);
			};
		} catch (e) {
			try {
				alert(message);
			} catch (_e) {}
			return () => {};
		}
	}
	async function postTimings(topicId, timings) {
		function readCsrfToken() {
			try {
				const meta = document.querySelector("meta[name=\"csrf-token\"]");
				if (meta && meta.content) return meta.content;
				const input = document.querySelector("input[name=\"authenticity_token\"]");
				if (input && input.value) return input.value;
				const match = document.cookie.match(/csrf_token=([^;]+)/);
				if (match) return decodeURIComponent(match[1]);
			} catch (e) {
				console.warn("[timingsBinder] failed to read csrf token", e);
			}
			return null;
		}
		const csrf = readCsrfToken() || "";
		const map = {};
		if (Array.isArray(timings)) for (let i = 0; i < timings.length; i++) map[i] = timings[i];
		else for (const k of Object.keys(timings)) {
			const key = Number(k);
			if (!Number.isNaN(key)) map[key] = timings[key];
		}
		const params = new URLSearchParams();
		let maxTime = 0;
		for (const idxStr of Object.keys(map)) {
			const idx = Number(idxStr);
			const val = String(map[idx]);
			params.append(`timings[${idx}]`, val);
			const num = Number(val);
			if (!Number.isNaN(num) && num > maxTime) maxTime = num;
		}
		params.append("topic_time", String(maxTime));
		params.append("topic_id", String(topicId));
		const url = "https://linux.do/topics/timings";
		const headers = {
			"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
			"x-requested-with": "XMLHttpRequest"
		};
		if (csrf) headers["x-csrf-token"] = csrf;
		return await fetch(url, {
			method: "POST",
			body: params.toString(),
			credentials: "same-origin",
			headers
		});
	}
	function sleep(ms) {
		return new Promise((resolve) => setTimeout(resolve, ms));
	}
	async function fetchPostsForTopic(topicId) {
		const url = `/t/${topicId}/posts.json`;
		const resp = await fetch(url, { credentials: "same-origin" });
		if (!resp.ok) throw new Error(`failed to fetch posts.json: ${resp.status}`);
		const data = await resp.json();
		let posts = [];
		let totalCount = 0;
		if (data && data.post_stream && Array.isArray(data.post_stream.posts)) {
			posts = data.post_stream.posts;
			if (posts.length > 0 && typeof posts[0].posts_count === "number") totalCount = posts[0].posts_count;
		}
		if ((!posts || posts.length === 0) && data && Array.isArray(data.posts)) posts = data.posts;
		if (!totalCount) {
			if (data && typeof data.highest_post_number === "number") totalCount = data.highest_post_number;
			else if (data && typeof data.posts_count === "number") totalCount = data.posts_count;
			else if (posts && posts.length > 0) totalCount = posts.length;
		}
		return {
			posts,
			totalCount
		};
	}
	async function autoReadAll(topicId) {
		try {
			let tid = topicId || 0;
			if (!tid) {
				const m1 = window.location.pathname.match(/t\/topic\/(\d+)/);
				const m2 = window.location.pathname.match(/t\/(\d+)/);
				if (m1 && m1[1]) tid = Number(m1[1]);
				else if (m2 && m2[1]) tid = Number(m2[1]);
				else {
					const el = document.querySelector("[data-topic-id]");
					if (el) tid = Number(el.getAttribute("data-topic-id")) || 0;
				}
			}
			if (!tid) {
				notify("无法推断 topic_id,自动阅读取消", "error");
				return;
			}
			notify(`开始自动阅读话题 ${tid} 的所有帖子...`, "info");
			const { posts, totalCount } = await fetchPostsForTopic(tid);
			if ((!posts || posts.length === 0) && !totalCount) {
				notify("未获取到任何帖子或总数信息", "error");
				return;
			}
			const total = totalCount || posts.length;
			const postNumbers = [];
			for (let n = 1; n <= total; n++) postNumbers.push(n);
			const BATCH_SIZE = 7;
			for (let i = 0; i < postNumbers.length; i += BATCH_SIZE) {
				const batch = postNumbers.slice(i, i + BATCH_SIZE);
				const timings = {};
				for (const pn of batch) timings[pn] = 1e3;
				try {
					await postTimings(tid, timings);
					notify(`已标记 ${Object.keys(timings).length} 个帖子为已读(发送)`, "success");
				} catch (e) {
					notify("发送阅读标记失败: " + (e && e.message ? e.message : String(e)), "error");
				}
				const delay = 500 + Math.floor(Math.random() * 1e3);
				await sleep(delay);
			}
			notify("自动阅读完成", "success");
		} catch (e) {
			notify("自动阅读异常: " + (e && e.message ? e.message : String(e)), "error");
		}
	}
	window.autoReadAllReplies = autoReadAll;
	function insertIntoEditor$1(text) {
		const textArea = document.querySelector("textarea.d-editor-input");
		const richEle = document.querySelector(".ProseMirror.d-editor-input");
		if (!textArea && !richEle) {
			console.error("找不到输入框");
			return;
		}
		if (textArea) {
			const start = textArea.selectionStart;
			const end = textArea.selectionEnd;
			const value = textArea.value;
			textArea.value = value.substring(0, start) + text + value.substring(end);
			textArea.setSelectionRange(start + text.length, start + text.length);
			textArea.focus();
			const event = new Event("input", { bubbles: true });
			textArea.dispatchEvent(event);
		} else if (richEle) {
			const selection = window.getSelection();
			if (selection && selection.rangeCount > 0) {
				const range = selection.getRangeAt(0);
				const textNode = document.createTextNode(text);
				range.insertNode(textNode);
				range.setStartAfter(textNode);
				range.setEndAfter(textNode);
				selection.removeAllRanges();
				selection.addRange(range);
			}
			richEle.focus();
		}
	}
	var ImageUploader = class {
		waitingQueue = [];
		uploadingQueue = [];
		failedQueue = [];
		successQueue = [];
		isProcessing = false;
		maxRetries = 2;
		progressDialog = null;
		async uploadImage(file) {
			return new Promise((resolve, reject) => {
				const item = {
					id: `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
					file,
					resolve,
					reject,
					retryCount: 0,
					status: "waiting",
					timestamp: Date.now()
				};
				this.waitingQueue.push(item);
				this.updateProgressDialog();
				this.processQueue();
			});
		}
		moveToQueue(item, targetStatus) {
			this.waitingQueue = this.waitingQueue.filter((i) => i.id !== item.id);
			this.uploadingQueue = this.uploadingQueue.filter((i) => i.id !== item.id);
			this.failedQueue = this.failedQueue.filter((i) => i.id !== item.id);
			this.successQueue = this.successQueue.filter((i) => i.id !== item.id);
			item.status = targetStatus;
			switch (targetStatus) {
				case "waiting":
					this.waitingQueue.push(item);
					break;
				case "uploading":
					this.uploadingQueue.push(item);
					break;
				case "failed":
					this.failedQueue.push(item);
					break;
				case "success":
					this.successQueue.push(item);
					break;
			}
			this.updateProgressDialog();
		}
		async processQueue() {
			if (this.isProcessing || this.waitingQueue.length === 0) return;
			this.isProcessing = true;
			while (this.waitingQueue.length > 0) {
				const item = this.waitingQueue.shift();
				if (!item) continue;
				this.moveToQueue(item, "uploading");
				try {
					const result = await this.performUpload(item.file);
					item.result = result;
					this.moveToQueue(item, "success");
					item.resolve(result);
					const markdown = `![${result.original_filename}](${result.url})`;
					insertIntoEditor$1(markdown);
				} catch (error) {
					item.error = error;
					if (this.shouldRetry(error, item)) {
						item.retryCount++;
						if (error.error_type === "rate_limit" && error.extras?.wait_seconds) await this.sleep(error.extras.wait_seconds * 1e3);
						else await this.sleep(Math.pow(2, item.retryCount) * 1e3);
						this.moveToQueue(item, "waiting");
					} else {
						this.moveToQueue(item, "failed");
						item.reject(error);
					}
				}
			}
			this.isProcessing = false;
		}
		shouldRetry(error, item) {
			if (item.retryCount >= this.maxRetries) return false;
			return error.error_type === "rate_limit";
		}
		retryFailedItem(itemId) {
			const item = this.failedQueue.find((i) => i.id === itemId);
			if (item && item.retryCount < this.maxRetries) {
				item.retryCount++;
				this.moveToQueue(item, "waiting");
				this.processQueue();
			}
		}
		showProgressDialog() {
			if (this.progressDialog) return;
			this.progressDialog = this.createProgressDialog();
			document.body.appendChild(this.progressDialog);
		}
		hideProgressDialog() {
			if (this.progressDialog) {
				this.progressDialog.remove();
				this.progressDialog = null;
			}
		}
		updateProgressDialog() {
			if (!this.progressDialog) return;
			const allItems = [
				...this.waitingQueue,
				...this.uploadingQueue,
				...this.failedQueue,
				...this.successQueue
			];
			this.renderQueueItems(this.progressDialog, allItems);
		}
		async sleep(ms) {
			return new Promise((resolve) => setTimeout(resolve, ms));
		}
		createProgressDialog() {
			const dialog = document.createElement("div");
			dialog.style.cssText = `
      position: fixed;
      top: 20px;
      right: 20px;
      width: 350px;
      max-height: 400px;
      background: white;
      border-radius: 8px;
      box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
      z-index: 10000;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      border: 1px solid #e5e7eb;
      overflow: hidden;
    `;
			const header = document.createElement("div");
			header.style.cssText = `
      padding: 16px 20px;
      background: #f9fafb;
      border-bottom: 1px solid #e5e7eb;
      font-weight: 600;
      font-size: 14px;
      color: #374151;
      display: flex;
      justify-content: space-between;
      align-items: center;
    `;
			header.textContent = "图片上传队列";
			const closeButton = document.createElement("button");
			closeButton.innerHTML = "✕";
			closeButton.style.cssText = `
      background: none;
      border: none;
      font-size: 16px;
      cursor: pointer;
      color: #6b7280;
      padding: 4px;
      border-radius: 4px;
      transition: background-color 0.2s;
    `;
			closeButton.addEventListener("click", () => {
				this.hideProgressDialog();
			});
			closeButton.addEventListener("mouseenter", () => {
				closeButton.style.backgroundColor = "#e5e7eb";
			});
			closeButton.addEventListener("mouseleave", () => {
				closeButton.style.backgroundColor = "transparent";
			});
			header.appendChild(closeButton);
			const content = document.createElement("div");
			content.className = "upload-queue-content";
			content.style.cssText = `
      max-height: 320px;
      overflow-y: auto;
      padding: 12px;
    `;
			dialog.appendChild(header);
			dialog.appendChild(content);
			return dialog;
		}
		renderQueueItems(dialog, allItems) {
			const content = dialog.querySelector(".upload-queue-content");
			if (!content) return;
			content.innerHTML = "";
			if (allItems.length === 0) {
				const emptyState = document.createElement("div");
				emptyState.style.cssText = `
        text-align: center;
        color: #6b7280;
        font-size: 14px;
        padding: 20px;
      `;
				emptyState.textContent = "暂无上传任务";
				content.appendChild(emptyState);
				return;
			}
			allItems.forEach((item) => {
				const itemEl = document.createElement("div");
				itemEl.style.cssText = `
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 8px 12px;
        margin-bottom: 8px;
        background: #f9fafb;
        border-radius: 6px;
        border-left: 4px solid ${this.getStatusColor(item.status)};
      `;
				const leftSide = document.createElement("div");
				leftSide.style.cssText = `
        flex: 1;
        min-width: 0;
      `;
				const fileName = document.createElement("div");
				fileName.style.cssText = `
        font-size: 13px;
        font-weight: 500;
        color: #374151;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      `;
				fileName.textContent = item.file.name;
				const status = document.createElement("div");
				status.style.cssText = `
        font-size: 12px;
        color: #6b7280;
        margin-top: 2px;
      `;
				status.textContent = this.getStatusText(item);
				leftSide.appendChild(fileName);
				leftSide.appendChild(status);
				const rightSide = document.createElement("div");
				rightSide.style.cssText = `
        display: flex;
        align-items: center;
        gap: 8px;
      `;
				if (item.status === "failed" && item.retryCount < this.maxRetries) {
					const retryButton = document.createElement("button");
					retryButton.innerHTML = "🔄";
					retryButton.style.cssText = `
          background: none;
          border: none;
          cursor: pointer;
          font-size: 14px;
          padding: 4px;
          border-radius: 4px;
          transition: background-color 0.2s;
        `;
					retryButton.title = "重试上传";
					retryButton.addEventListener("click", () => {
						this.retryFailedItem(item.id);
					});
					retryButton.addEventListener("mouseenter", () => {
						retryButton.style.backgroundColor = "#e5e7eb";
					});
					retryButton.addEventListener("mouseleave", () => {
						retryButton.style.backgroundColor = "transparent";
					});
					rightSide.appendChild(retryButton);
				}
				const statusIcon = document.createElement("div");
				statusIcon.style.cssText = `
        font-size: 16px;
      `;
				statusIcon.textContent = this.getStatusIcon(item.status);
				rightSide.appendChild(statusIcon);
				itemEl.appendChild(leftSide);
				itemEl.appendChild(rightSide);
				content.appendChild(itemEl);
			});
		}
		getStatusColor(status) {
			switch (status) {
				case "waiting": return "#f59e0b";
				case "uploading": return "#3b82f6";
				case "success": return "#10b981";
				case "failed": return "#ef4444";
				default: return "#6b7280";
			}
		}
		getStatusText(item) {
			switch (item.status) {
				case "waiting": return "等待上传";
				case "uploading": return "正在上传...";
				case "success": return "上传成功";
				case "failed":
					if (item.error?.error_type === "rate_limit") return `上传失败 - 请求过于频繁 (重试 ${item.retryCount}/${this.maxRetries})`;
					return `上传失败 (重试 ${item.retryCount}/${this.maxRetries})`;
				default: return "未知状态";
			}
		}
		getStatusIcon(status) {
			switch (status) {
				case "waiting": return "⏳";
				case "uploading": return "📤";
				case "success": return "✅";
				case "failed": return "❌";
				default: return "❓";
			}
		}
		async performUpload(file) {
			const sha1 = await this.calculateSHA1(file);
			const formData = new FormData();
			formData.append("upload_type", "composer");
			formData.append("relativePath", "null");
			formData.append("name", file.name);
			formData.append("type", file.type);
			formData.append("sha1_checksum", sha1);
			formData.append("file", file, file.name);
			const csrfToken = this.getCSRFToken();
			const headers = { "X-Csrf-Token": csrfToken };
			if (document.cookie) headers["Cookie"] = document.cookie;
			const response = await fetch(`https://linux.do/uploads.json?client_id=f06cb5577ba9410d94b9faf94e48c2d8`, {
				method: "POST",
				headers,
				body: formData
			});
			if (!response.ok) throw await response.json();
			return await response.json();
		}
		getCSRFToken() {
			const metaToken = document.querySelector("meta[name=\"csrf-token\"]");
			if (metaToken) return metaToken.content;
			const match = document.cookie.match(/csrf_token=([^;]+)/);
			if (match) return decodeURIComponent(match[1]);
			const hiddenInput = document.querySelector("input[name=\"authenticity_token\"]");
			if (hiddenInput) return hiddenInput.value;
			console.warn("[Image Uploader] No CSRF token found");
			return "";
		}
		async calculateSHA1(file) {
			const text = `${file.name}-${file.size}-${file.lastModified}`;
			const data = new TextEncoder().encode(text);
			if (crypto.subtle) try {
				const hashBuffer = await crypto.subtle.digest("SHA-1", data);
				return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
			} catch (e) {
				console.warn("[Image Uploader] Could not calculate SHA1, using fallback");
			}
			let hash = 0;
			for (let i = 0; i < text.length; i++) {
				const char = text.charCodeAt(i);
				hash = (hash << 5) - hash + char;
				hash = hash & hash;
			}
			return Math.abs(hash).toString(16).padStart(40, "0");
		}
	};
	var uploader = new ImageUploader();
	function extractEmojiFromImage(img, titleElement) {
		const url = img.src;
		if (!url || !url.startsWith("http")) return null;
		let name = "";
		const parts = (titleElement.textContent || "").split("·");
		if (parts.length > 0) name = parts[0].trim();
		if (!name || name.length < 2) name = img.alt || img.title || extractNameFromUrl(url);
		name = name.trim();
		if (name.length === 0) name = "表情";
		return {
			name,
			url
		};
	}
	function extractNameFromUrl(url) {
		try {
			const nameWithoutExt = (new URL(url).pathname.split("/").pop() || "").replace(/\.[^/.]+$/, "");
			const decoded = decodeURIComponent(nameWithoutExt);
			if (/^[0-9a-f]{8,}$/i.test(decoded) || decoded.length < 2) return "表情";
			return decoded || "表情";
		} catch {
			return "表情";
		}
	}
	function createAddButton(emojiData) {
		const link = createEl("a", {
			className: "image-source-link emoji-add-link",
			style: `
    color: #ffffff;
    text-decoration: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    font-size: inherit;
    font-family: inherit;
    background: linear-gradient(135deg, #4f46e5, #7c3aed);
    border: 2px solid #ffffff;
    border-radius: 6px;
    padding: 4px 8px;
    margin: 0 2px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    transition: all 0.2s ease;
    font-weight: 600;
  `
		});
		link.addEventListener("mouseenter", () => {
			if (!link.innerHTML.includes("已添加") && !link.innerHTML.includes("失败")) {
				link.style.background = "linear-gradient(135deg, #3730a3, #5b21b6)";
				link.style.transform = "scale(1.05)";
				link.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.3)";
			}
		});
		link.addEventListener("mouseleave", () => {
			if (!link.innerHTML.includes("已添加") && !link.innerHTML.includes("失败")) {
				link.style.background = "linear-gradient(135deg, #4f46e5, #7c3aed)";
				link.style.transform = "scale(1)";
				link.style.boxShadow = "0 2px 4px rgba(0, 0, 0, 0.2)";
			}
		});
		link.innerHTML = `
    <svg class="fa d-icon d-icon-plus svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 1em; height: 1em; fill: currentColor; margin-right: 4px;">
      <path d="M12 4c.55 0 1 .45 1 1v6h6c.55 0 1 .45 1 1s-.45 1-1 1h-6v6c0 .55-.45 1-1 1s-1-.45-1-1v-6H5c-.55 0-1-.45-1-1s.45-1 1-1h6V5c0-.55.45-1 1-1z"/>
    </svg>添加表情
  `;
		link.title = "添加到用户表情";
		link.addEventListener("click", async (e) => {
			e.preventDefault();
			e.stopPropagation();
			const originalHTML = link.innerHTML;
			const originalStyle = link.style.cssText;
			try {
				addEmojiToUserscript(emojiData);
				try {
					uploader.showProgressDialog();
				} catch (e$1) {
					console.warn("[Userscript] uploader.showProgressDialog failed:", e$1);
				}
				link.innerHTML = `
        <svg class="fa d-icon d-icon-check svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 1em; height: 1em; fill: currentColor; margin-right: 4px;">
          <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
        </svg>已添加
      `;
				link.style.background = "linear-gradient(135deg, #10b981, #059669)";
				link.style.color = "#ffffff";
				link.style.border = "2px solid #ffffff";
				link.style.boxShadow = "0 2px 4px rgba(16, 185, 129, 0.3)";
				setTimeout(() => {
					link.innerHTML = originalHTML;
					link.style.cssText = originalStyle;
				}, 2e3);
			} catch (error) {
				console.error("[Emoji Extension Userscript] Failed to add emoji:", error);
				link.innerHTML = `
        <svg class="fa d-icon d-icon-times svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 1em; height: 1em; fill: currentColor; margin-right: 4px;">
          <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
        </svg>失败
      `;
				link.style.background = "linear-gradient(135deg, #ef4444, #dc2626)";
				link.style.color = "#ffffff";
				link.style.border = "2px solid #ffffff";
				link.style.boxShadow = "0 2px 4px rgba(239, 68, 68, 0.3)";
				setTimeout(() => {
					link.innerHTML = originalHTML;
					link.style.cssText = originalStyle;
				}, 2e3);
			}
		});
		return link;
	}
	function processLightbox(lightbox) {
		if (lightbox.querySelector(".emoji-add-link")) return;
		const img = lightbox.querySelector(".mfp-img");
		const title = lightbox.querySelector(".mfp-title");
		if (!img || !title) return;
		const emojiData = extractEmojiFromImage(img, title);
		if (!emojiData) return;
		const addButton = createAddButton(emojiData);
		const sourceLink = title.querySelector("a.image-source-link");
		if (sourceLink) {
			const separator = document.createTextNode(" · ");
			title.insertBefore(separator, sourceLink);
			title.insertBefore(addButton, sourceLink);
		} else {
			title.appendChild(document.createTextNode(" · "));
			title.appendChild(addButton);
		}
	}
	function processAllLightboxes() {
		document.querySelectorAll(".mfp-wrap.mfp-gallery").forEach((lightbox) => {
			if (lightbox.classList.contains("mfp-wrap") && lightbox.classList.contains("mfp-gallery") && lightbox.querySelector(".mfp-img")) processLightbox(lightbox);
		});
	}
	function initOneClickAdd() {
		console.log("[Emoji Extension Userscript] Initializing one-click add functionality");
		setTimeout(processAllLightboxes, 500);
		new MutationObserver((mutations) => {
			let hasNewLightbox = false;
			mutations.forEach((mutation) => {
				if (mutation.type === "childList") mutation.addedNodes.forEach((node) => {
					if (node.nodeType === Node.ELEMENT_NODE) {
						const element = node;
						if (element.classList && element.classList.contains("mfp-wrap")) hasNewLightbox = true;
					}
				});
			});
			if (hasNewLightbox) setTimeout(processAllLightboxes, 100);
		}).observe(document.body, {
			childList: true,
			subtree: true
		});
		document.addEventListener("visibilitychange", () => {
			if (!document.hidden) setTimeout(processAllLightboxes, 200);
		});
	}
	function getBuildPlatform() {
		try {
			return "original";
		} catch {
			return "original";
		}
	}
	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() {
		const buildPlatform = getBuildPlatform();
		if (buildPlatform === "original") return detectRuntimePlatform();
		return buildPlatform;
	}
	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 getPlatformToolbarSelectors() {
		const platform = getEffectivePlatform();
		const baseSelectors = [".d-editor-button-bar[role=\"toolbar\"]", ".chat-composer__inner-container"];
		switch (platform) {
			case "mobile": return [
				...baseSelectors,
				".mobile-composer-toolbar",
				".chat-composer-mobile",
				"[data-mobile-toolbar]",
				".discourse-mobile .d-editor-button-bar"
			];
			case "pc": return [
				...baseSelectors,
				".desktop-composer-toolbar",
				".chat-composer-desktop",
				"[data-desktop-toolbar]",
				".discourse-desktop .d-editor-button-bar"
			];
			default: return baseSelectors;
		}
	}
	function logPlatformInfo() {
		const buildPlatform = getBuildPlatform();
		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);
	}
	function injectGlobalThemeStyles() {
		if (themeStylesInjected || typeof document === "undefined") return;
		themeStylesInjected = true;
		const style = document.createElement("style");
		style.id = "emoji-extension-theme-globals";
		style.textContent = `
    /* 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);
      }
    }
  `;
		document.head.appendChild(style);
	}
	var themeStylesInjected;
	var init_themeSupport = __esmMin((() => {
		themeStylesInjected = false;
	}));
	init_themeSupport();
	function injectEmojiPickerStyles() {
		if (typeof document === "undefined") return;
		if (document.getElementById("emoji-picker-styles")) return;
		injectGlobalThemeStyles();
		const css = `
.emoji-picker-hover-preview{
  position:fixed;
  pointer-events:none;
  display:none;
  z-index:1000002;
  max-width:320px;
  max-height:320px;
  overflow:hidden;
  border-radius:8px;
  box-shadow:0 6px 20px rgba(0,0,0,0.32);
  background:var(--emoji-preview-bg);
  padding:8px;
  transition:opacity .3s ease, transform .12s ease;
  border: 1px solid var(--emoji-preview-border);
  backdrop-filter: blur(10px);
}
.emoji-picker-hover-preview img.emoji-picker-hover-img{
  display:block;
  max-width:100%;
  max-height:220px;
  object-fit:contain;
}
.emoji-picker-hover-preview .emoji-picker-hover-label{
  font-size:12px;
  color:var(--emoji-preview-text);
  margin-top:8px;
  text-align:center;
  word-break:break-word;
  font-weight: 500;
}
`;
		const style = document.createElement("style");
		style.id = "emoji-picker-styles";
		style.textContent = css;
		document.head.appendChild(style);
	}
	function isImageUrl(value) {
		if (!value) return false;
		let v = value.trim();
		if (/^url\(/i.test(v)) {
			const inner = v.replace(/^url\(/i, "").replace(/\)$/, "").trim();
			if (inner.startsWith("\"") && inner.endsWith("\"") || inner.startsWith("'") && inner.endsWith("'")) v = inner.slice(1, -1).trim();
			else v = inner;
		}
		if (v.startsWith("data:image/")) return true;
		if (v.startsWith("blob:")) return true;
		if (v.startsWith("//")) v = "https:" + v;
		if (/\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)(\?.*)?$/i.test(v)) return true;
		try {
			const url = new URL(v);
			const protocol = url.protocol;
			if (protocol === "http:" || protocol === "https:" || protocol.endsWith(":")) {
				if (/\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)(\?.*)?$/i.test(url.pathname)) return true;
				if (/format=|ext=|type=image|image_type=/i.test(url.search)) return true;
			}
		} catch {}
		return false;
	}
	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 injectManagerStyles() {
		if (__managerStylesInjected) return;
		__managerStylesInjected = true;
		document.head.appendChild(createEl("style", {
			attrs: { "data-emoji-manager-styles": "1" },
			text: `
    /* Modal backdrop */
    .emoji-manager-wrapper {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0,0,0,0.8);
      z-index: 999999;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    /* Main modal panel */
    .emoji-manager-panel {
      background: white;
      border-radius: 8px;
      max-width: 90vw;
      max-height: 90vh;
      width: 1000px;
      height: 600px;
      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);
    }

    /* Left panel - groups list */
    .emoji-manager-left {
      background: #f8f9fa;
      border-right: 1px solid #e9ecef;
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }

    .emoji-manager-left-header {
      display: flex;
      align-items: center;
      padding: 16px;
      border-bottom: 1px solid #e9ecef;
      background: white;
    }

    .emoji-manager-addgroup-row {
      display: flex;
      gap: 8px;
      padding: 12px;
      border-bottom: 1px solid #e9ecef;
    }

    .emoji-manager-groups-list {
      flex: 1;
      overflow-y: auto;
      padding: 8px;
    }

    .emoji-manager-groups-list > div {
      padding: 12px;
      border-radius: 6px;
      cursor: pointer;
      margin-bottom: 4px;
      transition: background-color 0.2s;
    }

    .emoji-manager-groups-list > div:hover {
      background: #e9ecef;
    }

    .emoji-manager-groups-list > div:focus {
      outline: none;
      box-shadow: inset 0 0 0 2px #007bff;
    }

    /* Right panel - emoji display and editing */
    .emoji-manager-right {
      background: white;
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }

    .emoji-manager-right-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 16px;
      border-bottom: 1px solid #e9ecef;
    }

    .emoji-manager-right-main {
      flex: 1;
      overflow-y: auto;
      padding: 16px;
    }

    .emoji-manager-emojis {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
      gap: 12px;
      margin-bottom: 16px;
    }

    .emoji-manager-card {
      display: flex;
      flex-direction: column;
      gap: 8px;
      align-items: center;
      padding: 12px;
      background: #f8f9fa;
      border: 1px solid #e9ecef;
      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);
    }

    .emoji-manager-card-img {
      width: 80px;
      height: 80px;
      /* Prevent extremely large images from breaking the layout by limiting their
         rendered size relative to the card. Use both absolute and percentage-based
         constraints so user-provided pixel sizes (from edit form) still work but
         will not overflow the card or modal. */
      max-width: 90%;
      max-height: 60vh; /* allow tall images but cap at viewport height */
      object-fit: contain;
      border-radius: 6px;
      background: white;
    }

    .emoji-manager-card-name {
      font-size: 12px;
      color: #495057;
      text-align: center;
      width: 100%;
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
      font-weight: 500;
    }

    .emoji-manager-card-actions {
      display: flex;
      gap: 6px;
    }

    /* Add emoji form */
    .emoji-manager-add-emoji-form {
      padding: 16px;
      background: #f8f9fa;
      border-top: 1px solid #e9ecef;
      display: flex;
      gap: 8px;
      align-items: center;
    }

    /* Footer */
    .emoji-manager-footer {
      grid-column: 1 / -1;
      display: flex;
      gap: 8px;
      justify-content: space-between;
      padding: 16px;
      background: #f8f9fa;
      border-top: 1px solid #e9ecef;
    }

    /* Editor panel - popup modal */
    .emoji-manager-editor-panel {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background: white;
      border: 1px solid #e9ecef;
      border-radius: 8px;
      padding: 24px;
      box-shadow: 0 10px 40px rgba(0,0,0,0.3);
      z-index: 1000000;
      min-width: 400px;
    }

    .emoji-manager-editor-preview {
      width: 100px;
      height: 100px;
      /* editor preview should be bounded to avoid huge remote images
         while still allowing percentage-based scaling */
      max-width: 100%;
      max-height: 40vh;
      object-fit: contain;
      border-radius: 8px;
      background: #f8f9fa;
      margin: 0 auto 16px;
      display: block;
    }

    /* Hover preview (moved from inline styles) */
    .emoji-manager-hover-preview {
      position: fixed;
      pointer-events: none;
      z-index: 1000002;
      display: none;
      /* For hover previews allow a generous but bounded size relative to viewport
         to avoid covering entire UI or pushing content off-screen. */
      max-width: 30vw;
      max-height: 40vh;
      width: auto;
      height: auto;
      border: 1px solid rgba(0,0,0,0.1);
      background: #fff;
      padding: 4px;
      border-radius: 6px;
      box-shadow: 0 6px 18px rgba(0,0,0,0.12);
    }

    /* Form styling */
    .form-control {
      width: 100%;
      padding: 8px 12px;
      border: 1px solid #ced4da;
      border-radius: 4px;
      font-size: 14px;
      margin-bottom: 8px;
    }

    .btn {
      padding: 8px 16px;
      border: 1px solid transparent;
      border-radius: 4px;
      font-size: 14px;
      cursor: pointer;
      transition: all 0.2s;
    }

    .btn-primary {
      background-color: #007bff;
      color: white;
    }

    .btn-primary:hover {
      background-color: #0056b3;
    }

    .btn-sm {
      padding: 4px 8px;
      font-size: 12px;
    }
  `
		}));
	}
	var __managerStylesInjected;
	var init_styles = __esmMin((() => {
		init_createEl();
		__managerStylesInjected = false;
	}));
	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) {
				alert("名称和 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 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"
		});
		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 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"
		});
		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 = "";
			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);
			});
		}
		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"
				});
				if (emo.width) img.style.width = typeof emo.width === "number" ? emo.width + "px" : emo.width;
				if (emo.height) img.style.height = typeof emo.height === "number" ? emo.height + "px" : emo.height;
				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"
				});
				edit.addEventListener("click", () => {
					showEditorFor(group.id, idx);
				});
				const del = createEl("button", {
					text: "删除",
					className: "btn btn-sm"
				});
				del.addEventListener("click", () => {
					group.emojis.splice(idx, 1);
					renderGroups();
					renderSelectedGroup();
				});
				actions.appendChild(edit);
				actions.appendChild(del);
				card.appendChild(img);
				card.appendChild(name);
				card.appendChild(actions);
				emojisContainer.appendChild(card);
				bindHoverPreview(img, emo);
			});
		}
		let hoverPreviewEl = null;
		function ensureHoverPreview$1() {
			if (hoverPreviewEl && document.body.contains(hoverPreviewEl)) return hoverPreviewEl;
			hoverPreviewEl = createEl("img", { className: "emoji-manager-hover-preview" });
			document.body.appendChild(hoverPreviewEl);
			return hoverPreviewEl;
		}
		function bindHoverPreview(targetImg, emo) {
			const preview = ensureHoverPreview$1();
			function onEnter(e) {
				preview.src = emo.url;
				if (emo.width) preview.style.width = typeof emo.width === "number" ? emo.width + "px" : emo.width;
				else preview.style.width = "";
				if (emo.height) preview.style.height = typeof emo.height === "number" ? emo.height + "px" : emo.height;
				else preview.style.height = "";
				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() {
				if (preview) preview.style.display = "none";
			}
			targetImg.addEventListener("mouseenter", onEnter);
			targetImg.addEventListener("mousemove", movePreview);
			targetImg.addEventListener("mouseleave", onLeave);
		}
		addGroupBtn.addEventListener("click", () => {
			const id = (addGroupInput.value || "").trim();
			if (!id) return alert("请输入分组 id");
			if (userscriptState.emojiGroups.find((g) => g.id === id)) return alert("分组已存在");
			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();
		});
		addEmojiBtn.addEventListener("click", () => {
			if (!selectedGroupId) return alert("请先选择分组");
			const url = (emojiUrlInput.value || "").trim();
			const name = (emojiNameInput.value || "").trim();
			const widthVal = (emojiWidthInput.value || "").trim();
			const heightVal = (emojiHeightInput.value || "").trim();
			const width = widthVal ? parseInt(widthVal, 10) : NaN;
			const height = heightVal ? parseInt(heightVal, 10) : NaN;
			if (!url || !name) return alert("请输入 url 和 名称");
			const group = userscriptState.emojiGroups.find((g) => g.id === selectedGroupId);
			if (!group) return;
			group.emojis = group.emojis || [];
			const newEmo = {
				url,
				name
			};
			if (!isNaN(width) && width > 0) newEmo.width = width;
			if (!isNaN(height) && height > 0) newEmo.height = height;
			group.emojis.push(newEmo);
			emojiUrlInput.value = "";
			emojiNameInput.value = "";
			emojiWidthInput.value = "";
			emojiHeightInput.value = "";
			renderGroups();
			renderSelectedGroup();
		});
		deleteGroupBtn.addEventListener("click", () => {
			if (!selectedGroupId) return alert("请先选择分组");
			const idx = userscriptState.emojiGroups.findIndex((g) => g.id === selectedGroupId);
			if (idx >= 0) {
				if (!confirm("确认删除该分组?该操作不可撤销")) 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", () => {
			const data = exportUserscriptData();
			navigator.clipboard.writeText(data).then(() => alert("已复制到剪贴板")).catch(() => {
				const ta = createEl("textarea", { value: data });
				document.body.appendChild(ta);
				ta.select();
			});
		});
		importBtn.addEventListener("click", () => {
			const ta = createEl("textarea", {
				placeholder: "粘贴 JSON 后点击确认",
				style: "width:100%;height:200px;margin-top:8px;"
			});
			const ok = createEl("button", {
				text: "确认导入",
				style: "padding:6px 8px;margin-top:6px;"
			});
			const container = createEl("div");
			container.appendChild(ta);
			container.appendChild(ok);
			const importModal = createEl("div", { style: "position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:1000001;" });
			const box = createEl("div", { style: "background:#fff;padding:12px;border-radius:6px;width:90%;max-width:700px;" });
			box.appendChild(container);
			importModal.appendChild(box);
			document.body.appendChild(importModal);
			ok.addEventListener("click", () => {
				try {
					const json = ta.value.trim();
					if (!json) return;
					if (importUserscriptData(json)) {
						alert("导入成功,请保存以持久化");
						loadDataFromLocalStorage$1();
						renderGroups();
						renderSelectedGroup();
					} else alert("导入失败:格式错误");
				} catch (e) {
					alert("导入异常:" + e);
				}
				importModal.remove();
			});
		});
		saveBtn.addEventListener("click", () => {
			try {
				saveDataToLocalStorage({ emojiGroups: userscriptState.emojiGroups });
				alert("已保存");
			} catch (e) {
				alert("保存失败:" + e);
			}
		});
		syncBtn.addEventListener("click", () => {
			try {
				if (syncFromManager()) {
					alert("同步成功,已导入管理器数据");
					loadDataFromLocalStorage$1();
					renderGroups();
					renderSelectedGroup();
				} else alert("同步未成功,未检测到管理器数据");
			} catch (e) {
				alert("同步异常:" + 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();
		}
	}
	function loadDataFromLocalStorage$1() {
		console.log("Data reload requested");
	}
	var init_manager = __esmMin((() => {
		init_styles();
		init_createEl();
		init_userscript_storage();
	}));
	function showGroupEditorModal() {
		injectGlobalThemeStyles();
		const modal = createEl("div", { style: `
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.8);
      z-index: 999999;
      display: flex;
      align-items: center;
      justify-content: center;
    ` });
		const content = createEl("div", { style: `
      background: var(--emoji-modal-bg);
      color: var(--emoji-modal-text);
      border-radius: 8px;
      padding: 24px;
      max-width: 700px;
      max-height: 80vh;
      overflow-y: auto;
      position: relative;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
    ` });
		content.innerHTML = `
    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
      <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: 20px; padding: 16px; 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="font-size: 14px; color: var(--emoji-modal-text); opacity: 0.8; line-height: 1.4;">
        • 点击分组名称或图标进行编辑<br>
        • 图标支持 emoji 字符或单个字符<br>
        • 修改会立即保存到本地存储<br>
        • 可以调整分组的显示顺序
      </div>
    </div>

    <div id="groupsList" style="display: flex; flex-direction: column; gap: 12px;">
      ${userscriptState.emojiGroups.map((group, index) => `
        <div class="group-item" data-group-id="${group.id}" data-index="${index}" style="
          display: flex;
          align-items: center;
          gap: 12px;
          padding: 16px;
          background: var(--emoji-modal-button-bg);
          border: 1px solid var(--emoji-modal-border);
          border-radius: 6px;
          transition: all 0.2s;
        ">
          <div class="drag-handle" style="
            cursor: grab;
            color: var(--emoji-modal-text);
            opacity: 0.5;
            font-size: 16px;
            user-select: none;
          " title="拖拽调整顺序">⋮⋮</div>

          <div class="group-icon-editor" style="
            min-width: 40px;
            height: 40px;
            display: flex;
            align-items: center;
            justify-content: center;
            background: var(--emoji-modal-bg);
            border: 1px dashed var(--emoji-modal-border);
            border-radius: 4px;
            cursor: pointer;
            font-size: 18px;
            user-select: none;
          " data-group-id="${group.id}" title="点击编辑图标">
            ${group.icon || "📁"}
          </div>

          <div style="flex: 1; display: flex; flex-direction: column; gap: 4px;">
            <input class="group-name-editor"
                   type="text"
                   value="${group.name || "Unnamed Group"}"
                   data-group-id="${group.id}"
                   style="
                     background: var(--emoji-modal-bg);
                     color: var(--emoji-modal-text);
                     border: 1px solid var(--emoji-modal-border);
                     border-radius: 4px;
                     padding: 8px 12px;
                     font-size: 14px;
                     font-weight: 500;
                   "
                   placeholder="分组名称">
            <div style="font-size: 12px; color: var(--emoji-modal-text); opacity: 0.6;">
              ID: ${group.id} | 表情数: ${group.emojis ? group.emojis.length : 0}
            </div>
          </div>

          <div style="display: flex; flex-direction: column; gap: 4px; align-items: 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: 4px 8px;
              cursor: pointer;
              font-size: 12px;
              color: var(--emoji-modal-text);
            " ${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: 4px 8px;
              cursor: pointer;
              font-size: 12px;
              color: var(--emoji-modal-text);
            " ${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: flex-end;">
      <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>
  `;
		modal.appendChild(content);
		document.body.appendChild(modal);
		const style = document.createElement("style");
		style.textContent = `
    .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;
    }
  `;
		document.head.appendChild(style);
		content.querySelector("#closeModal")?.addEventListener("click", () => {
			modal.remove();
			style.remove();
		});
		modal.addEventListener("click", (e) => {
			if (e.target === modal) {
				modal.remove();
				style.remove();
			}
		});
		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$1(`分组 "${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) {
					const newIcon = prompt("请输入新的图标字符 (emoji 或单个字符):", target.textContent || "📁");
					if (newIcon && newIcon.trim()) {
						const group = userscriptState.emojiGroups.find((g) => g.id === groupId);
						if (group) {
							group.icon = newIcon.trim();
							target.textContent = newIcon.trim();
							showTemporaryMessage$1(`分组图标已更新为: ${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();
					style.remove();
					showTemporaryMessage$1("分组顺序已调整");
					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();
					style.remove();
					showTemporaryMessage$1("分组顺序已调整");
					setTimeout(() => showGroupEditorModal(), 300);
				}
			});
		});
		content.querySelector("#addNewGroup")?.addEventListener("click", () => {
			const groupName = prompt("请输入新分组的名称:");
			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();
				style.remove();
				showTemporaryMessage$1(`新分组 "${groupName.trim()}" 已创建`);
				setTimeout(() => showGroupEditorModal(), 300);
			}
		});
		content.querySelector("#saveAllChanges")?.addEventListener("click", () => {
			saveDataToLocalStorage({ emojiGroups: userscriptState.emojiGroups });
			showTemporaryMessage$1("所有更改已保存到本地存储");
		});
	}
	function showTemporaryMessage$1(message) {
		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 = document.createElement("style");
			style.id = "tempMessageStyles";
			style.textContent = `
      @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(() => {
			messageEl.remove();
		}, 2e3);
	}
	var init_groupEditor = __esmMin((() => {
		init_state();
		init_userscript_storage();
		init_createEl();
		init_themeSupport();
	}));
	function showPopularEmojisModal() {
		injectGlobalThemeStyles();
		const modal = createEl("div", { style: `
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.8);
      z-index: 999999;
      display: flex;
      align-items: center;
      justify-content: center;
    ` });
		const content = createEl("div", { style: `
      background: var(--emoji-modal-bg);
      color: var(--emoji-modal-text);
      border-radius: 8px;
      padding: 24px;
      max-width: 600px;
      max-height: 80vh;
      overflow-y: auto;
      position: relative;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
    ` });
		const popularEmojis = getPopularEmojis(50);
		content.innerHTML = `
    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
      <h2 style="margin: 0; color: var(--emoji-modal-text);">常用表情 (${popularEmojis.length})</h2>
      <div style="display: flex; gap: 8px; align-items: center;">
        <button id="clearStats" style="padding: 6px 12px; background: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">清空统计</button>
        <button id="closeModal" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #999;">×</button>
      </div>
    </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="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
        <span style="font-weight: 500; color: var(--emoji-modal-label);">表情按使用次数排序</span>
        <span style="font-size: 12px; color: var(--emoji-modal-text); opacity: 0.7;">点击表情直接使用</span>
      </div>
      <div style="font-size: 12px; color: var(--emoji-modal-text); opacity: 0.6;">
        总使用次数: ${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); opacity: 0.7;\">还没有使用过表情<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>
    ` : ""}
  `;
		modal.appendChild(content);
		document.body.appendChild(modal);
		const style = document.createElement("style");
		style.textContent = `
    .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);
    }
  `;
		document.head.appendChild(style);
		content.querySelector("#closeModal")?.addEventListener("click", () => {
			modal.remove();
			style.remove();
		});
		content.querySelector("#clearStats")?.addEventListener("click", () => {
			if (confirm("确定要清空所有表情使用统计吗?此操作不可撤销。")) {
				clearEmojiUsageStats();
				modal.remove();
				style.remove();
				showTemporaryMessage("表情使用统计已清空");
				setTimeout(() => showPopularEmojisModal(), 300);
			}
		});
		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();
					style.remove();
					showTemporaryMessage(`已使用表情: ${name}`);
				}
			});
		});
		modal.addEventListener("click", (e) => {
			if (e.target === modal) {
				modal.remove();
				style.remove();
			}
		});
	}
	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 }));
				}
			}
		}
	}
	function showTemporaryMessage(message) {
		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 = document.createElement("style");
			style.id = "tempMessageStyles";
			style.textContent = `
      @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(() => {
			messageEl.remove();
		}, 2e3);
	}
	var init_popularEmojis = __esmMin((() => {
		init_state();
		init_userscript_storage();
		init_createEl();
		init_themeSupport();
	}));
	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%;
    background: rgba(0, 0, 0, 0.8);
    z-index: 999999;
    display: flex;
    align-items: center;
    justify-content: center;
  ` });
		const content = createEl("div", {
			style: `
      background: var(--emoji-modal-bg);
      color: var(--emoji-modal-text);
      border-radius: 8px;
    padding: 24px;
    max-width: 500px;
    max-height: 80vh;
    overflow-y: auto;
    position: relative;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  `,
			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="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;
          border-radius: 4px;
          cursor: pointer;
          font-size: 12px;
        ">编辑分组</button>
        <button id="openPopularEmojis" style="
          padding: 6px 12px;
          background: var(--emoji-modal-primary-bg);
          color: white;
          border: none;
          border-radius: 4px;
          cursor: pointer;
          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>
  `
		});
		modal.appendChild(content);
		document.body.appendChild(modal);
		const scaleSlider = content.querySelector("#scaleSlider");
		const scaleValue = content.querySelector("#scaleValue");
		scaleSlider?.addEventListener("input", () => {
			if (scaleValue) scaleValue.textContent = scaleSlider.value + "%";
		});
		content.querySelector("#closeModal")?.addEventListener("click", () => {
			modal.remove();
		});
		content.querySelector("#resetSettings")?.addEventListener("click", async () => {
			if (confirm("确定要重置所有设置吗?")) {
				userscriptState.settings = {
					imageScale: 30,
					gridColumns: 4,
					outputFormat: "markdown",
					forceMobileMode: false,
					defaultGroup: "nachoneko",
					showSearchBar: true,
					enableFloatingPreview: true
				};
				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 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();
		});
		modal.addEventListener("click", (e) => {
			if (e.target === modal) modal.remove();
		});
	}
	var init_settings = __esmMin((() => {
		init_state();
		init_userscript_storage();
		init_createEl();
		init_themeSupport();
		init_groupEditor();
		init_popularEmojis();
	}));
	init_state();
	init_userscript_storage();
	init_createEl();
	function isMobileView() {
		try {
			return getEffectivePlatform() === "mobile" || !!(userscriptState && userscriptState.settings && userscriptState.settings.forceMobileMode);
		} catch (e) {
			return false;
		}
	}
	function insertEmojiIntoEditor(emoji) {
		console.log("[Emoji Extension Userscript] Inserting emoji:", emoji);
		if (emoji.name && emoji.url) trackEmojiUsage(emoji.name, emoji.url);
		const textarea = document.querySelector("textarea.d-editor-input");
		const proseMirror = document.querySelector(".ProseMirror.d-editor-input");
		if (!textarea && !proseMirror) {
			console.error("找不到输入框");
			return;
		}
		const dimensionMatch = emoji.url?.match(/_(\d{3,})x(\d{3,})\./);
		let width = "500";
		let height = "500";
		if (dimensionMatch) {
			width = dimensionMatch[1];
			height = dimensionMatch[2];
		} else if (emoji.width && emoji.height) {
			width = emoji.width.toString();
			height = emoji.height.toString();
		}
		const scale = userscriptState.settings?.imageScale || 30;
		const outputFormat = userscriptState.settings?.outputFormat || "markdown";
		if (textarea) {
			let insertText = "";
			if (outputFormat === "html") {
				const scaledWidth = Math.max(1, Math.round(Number(width) * (scale / 100)));
				const scaledHeight = Math.max(1, Math.round(Number(height) * (scale / 100)));
				insertText = `<img src="${emoji.url}" title=":${emoji.name}:" class="emoji only-emoji" alt=":${emoji.name}:" loading="lazy" width="${scaledWidth}" height="${scaledHeight}" style="aspect-ratio: ${scaledWidth} / ${scaledHeight};"> `;
			} else insertText = `![${emoji.name}|${width}x${height},${scale}%](${emoji.url}) `;
			const selectionStart = textarea.selectionStart;
			const selectionEnd = textarea.selectionEnd;
			textarea.value = textarea.value.substring(0, selectionStart) + insertText + textarea.value.substring(selectionEnd, textarea.value.length);
			textarea.selectionStart = textarea.selectionEnd = selectionStart + insertText.length;
			textarea.focus();
			const inputEvent = new Event("input", {
				bubbles: true,
				cancelable: true
			});
			textarea.dispatchEvent(inputEvent);
		} else if (proseMirror) {
			const imgWidth = Number(width) || 500;
			const scaledWidth = Math.max(1, Math.round(imgWidth * (scale / 100)));
			const htmlContent = `<img src="${emoji.url}" alt="${emoji.name}" width="${width}" height="${height}" data-scale="${scale}" style="width: ${scaledWidth}px">`;
			try {
				const dataTransfer = new DataTransfer();
				dataTransfer.setData("text/html", htmlContent);
				const pasteEvent = new ClipboardEvent("paste", {
					clipboardData: dataTransfer,
					bubbles: true
				});
				proseMirror.dispatchEvent(pasteEvent);
			} catch (error) {
				try {
					document.execCommand("insertHTML", false, htmlContent);
				} catch (fallbackError) {
					console.error("无法向富文本编辑器中插入表情", fallbackError);
				}
			}
		}
	}
	var _hoverPreviewEl = null;
	function ensureHoverPreview() {
		if (_hoverPreviewEl && document.body.contains(_hoverPreviewEl)) return _hoverPreviewEl;
		_hoverPreviewEl = 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:#fff;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:#333;margin-top:6px;text-align:center;"
		});
		_hoverPreviewEl.appendChild(img);
		_hoverPreviewEl.appendChild(label);
		document.body.appendChild(_hoverPreviewEl);
		return _hoverPreviewEl;
	}
	function createMobileEmojiPicker(groups) {
		const modal = createEl("div", {
			className: "modal d-modal fk-d-menu-modal emoji-picker-content",
			attrs: {
				"data-identifier": "emoji-picker",
				"data-keyboard": "false",
				"aria-modal": "true",
				role: "dialog"
			}
		});
		const modalContainerDiv = createEl("div", { className: "d-modal__container" });
		const modalBody = createEl("div", { className: "d-modal__body" });
		modalBody.tabIndex = -1;
		const emojiPickerDiv = createEl("div", { className: "emoji-picker" });
		const filterContainer = createEl("div", { className: "emoji-picker__filter-container" });
		const filterInputContainer = createEl("div", { className: "emoji-picker__filter filter-input-container" });
		const filterInput = createEl("input", {
			className: "filter-input",
			placeholder: "按表情符号名称搜索…",
			type: "text"
		});
		filterInputContainer.appendChild(filterInput);
		const closeButton = createEl("button", {
			className: "btn no-text btn-icon btn-transparent emoji-picker__close-btn",
			type: "button",
			innerHTML: `<svg class="fa d-icon d-icon-xmark svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><use href="#xmark"></use></svg>`
		});
		closeButton.addEventListener("click", () => {
			const container = modal.closest(".modal-container") || modal;
			if (container) container.remove();
		});
		filterContainer.appendChild(filterInputContainer);
		filterContainer.appendChild(closeButton);
		const content = createEl("div", { className: "emoji-picker__content" });
		const sectionsNav = createEl("div", { className: "emoji-picker__sections-nav" });
		const managementButton = createEl("button", {
			className: "btn no-text btn-flat emoji-picker__section-btn management-btn",
			attrs: {
				tabindex: "-1",
				style: "border-right: 1px solid #ddd;"
			},
			innerHTML: "⚙️",
			title: "管理表情 - 点击打开完整管理界面",
			type: "button"
		});
		managementButton.addEventListener("click", () => {
			__vitePreload(async () => {
				const { openManagementInterface: openManagementInterface$1 } = await Promise.resolve().then(() => (init_manager(), manager_exports));
				return { openManagementInterface: openManagementInterface$1 };
			}, void 0).then(({ openManagementInterface: openManagementInterface$1 }) => {
				openManagementInterface$1();
			});
		});
		sectionsNav.appendChild(managementButton);
		const settingsButton = createEl("button", {
			className: "btn no-text btn-flat emoji-picker__section-btn settings-btn",
			innerHTML: "🔧",
			title: "设置",
			attrs: {
				tabindex: "-1",
				style: "border-right: 1px solid #ddd;"
			},
			type: "button"
		});
		settingsButton.addEventListener("click", () => {
			__vitePreload(async () => {
				const { showSettingsModal: showSettingsModal$1 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
				return { showSettingsModal: showSettingsModal$1 };
			}, void 0).then(({ showSettingsModal: showSettingsModal$1 }) => {
				showSettingsModal$1();
			});
		});
		sectionsNav.appendChild(settingsButton);
		const scrollableContent = createEl("div", { className: "emoji-picker__scrollable-content" });
		const sections = createEl("div", {
			className: "emoji-picker__sections",
			attrs: { role: "button" }
		});
		let hoverPreviewEl = null;
		function ensureHoverPreview$1() {
			if (hoverPreviewEl && document.body.contains(hoverPreviewEl)) return hoverPreviewEl;
			hoverPreviewEl = 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:#fff;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:#333;margin-top:6px;text-align:center;"
			});
			hoverPreviewEl.appendChild(img);
			hoverPreviewEl.appendChild(label);
			document.body.appendChild(hoverPreviewEl);
			return hoverPreviewEl;
		}
		groups.forEach((group, index) => {
			if (!group?.emojis?.length) return;
			const navButton = createEl("button", {
				className: `btn no-text btn-flat emoji-picker__section-btn ${index === 0 ? "active" : ""}`,
				attrs: {
					tabindex: "-1",
					"data-section": group.id,
					type: "button"
				}
			});
			const iconVal = group.icon || "📁";
			if (isImageUrl(iconVal)) {
				const img = createEl("img", {
					src: iconVal,
					alt: group.name || "",
					className: "emoji",
					style: "width: 18px; height: 18px; object-fit: contain;"
				});
				navButton.appendChild(img);
			} else navButton.textContent = String(iconVal);
			navButton.title = group.name;
			navButton.addEventListener("click", () => {
				sectionsNav.querySelectorAll(".emoji-picker__section-btn").forEach((btn) => btn.classList.remove("active"));
				navButton.classList.add("active");
				const target = sections.querySelector(`[data-section="${group.id}"]`);
				if (target) target.scrollIntoView({
					behavior: "smooth",
					block: "start"
				});
			});
			sectionsNav.appendChild(navButton);
			const section = createEl("div", {
				className: "emoji-picker__section",
				attrs: {
					"data-section": group.id,
					role: "region",
					"aria-label": group.name
				}
			});
			const titleContainer = createEl("div", { className: "emoji-picker__section-title-container" });
			const title = createEl("h2", {
				className: "emoji-picker__section-title",
				text: group.name
			});
			titleContainer.appendChild(title);
			const sectionEmojis = createEl("div", { className: "emoji-picker__section-emojis" });
			group.emojis.forEach((emoji) => {
				if (!emoji || typeof emoji !== "object" || !emoji.url || !emoji.name) return;
				const img = createEl("img", {
					src: emoji.url,
					alt: emoji.name,
					className: "emoji",
					title: `:${emoji.name}:`,
					style: "width: 32px; height: 32px; object-fit: contain;",
					attrs: {
						"data-emoji": emoji.name,
						tabindex: "0",
						loading: "lazy"
					}
				});
				(function bindHover(imgEl, emo) {
					if (!userscriptState.settings?.enableFloatingPreview) return;
					const preview = ensureHoverPreview$1();
					const previewImg = preview.querySelector("img");
					const previewLabel = preview.querySelector(".emoji-picker-hover-label");
					let fadeTimer = null;
					function onEnter(e) {
						previewImg.src = emo.url;
						previewLabel.textContent = emo.name || "";
						preview.style.display = "block";
						preview.style.opacity = "1";
						preview.style.transition = "opacity 0.12s ease, transform 0.12s ease";
						if (fadeTimer) {
							clearTimeout(fadeTimer);
							fadeTimer = null;
						}
						fadeTimer = window.setTimeout(() => {
							preview.style.opacity = "0";
							setTimeout(() => {
								if (preview.style.opacity === "0") preview.style.display = "none";
							}, 300);
						}, 5e3);
						move(e);
					}
					function move(e) {
						const pad = 12;
						const vw = window.innerWidth;
						const vh = window.innerHeight;
						const rect = preview.getBoundingClientRect();
						let left = e.clientX + pad;
						let top = e.clientY + pad;
						if (left + rect.width > vw) left = e.clientX - rect.width - pad;
						if (top + rect.height > vh) top = e.clientY - rect.height - pad;
						preview.style.left = left + "px";
						preview.style.top = top + "px";
					}
					function onLeave() {
						if (fadeTimer) {
							clearTimeout(fadeTimer);
							fadeTimer = null;
						}
						preview.style.display = "none";
					}
					imgEl.addEventListener("mouseenter", onEnter);
					imgEl.addEventListener("mousemove", move);
					imgEl.addEventListener("mouseleave", onLeave);
				})(img, emoji);
				img.addEventListener("click", () => {
					insertEmojiIntoEditor(emoji);
					const modalContainer = modal.closest(".modal-container");
					if (modalContainer) modalContainer.remove();
					else modal.remove();
				});
				img.addEventListener("keydown", (e) => {
					if (e.key === "Enter" || e.key === " ") {
						e.preventDefault();
						insertEmojiIntoEditor(emoji);
						const modalContainer = modal.closest(".modal-container");
						if (modalContainer) modalContainer.remove();
						else modal.remove();
					}
				});
				sectionEmojis.appendChild(img);
			});
			section.appendChild(titleContainer);
			section.appendChild(sectionEmojis);
			sections.appendChild(section);
		});
		filterInput.addEventListener("input", (e) => {
			const q = (e.target.value || "").toLowerCase();
			sections.querySelectorAll("img").forEach((img) => {
				const emojiName = (img.dataset.emoji || "").toLowerCase();
				img.style.display = q === "" || emojiName.includes(q) ? "" : "none";
			});
			sections.querySelectorAll(".emoji-picker__section").forEach((section) => {
				const visibleEmojis = section.querySelectorAll("img:not([style*=\"display: none\"])");
				section.style.display = visibleEmojis.length > 0 ? "" : "none";
			});
		});
		scrollableContent.appendChild(sections);
		content.appendChild(sectionsNav);
		content.appendChild(scrollableContent);
		emojiPickerDiv.appendChild(filterContainer);
		emojiPickerDiv.appendChild(content);
		modalBody.appendChild(emojiPickerDiv);
		modalContainerDiv.appendChild(modalBody);
		modal.appendChild(modalContainerDiv);
		return modal;
	}
	function createDesktopEmojiPicker(groups) {
		const picker = createEl("div", {
			className: "fk-d-menu -animated -expanded",
			style: "max-width: 400px; visibility: visible; z-index: 999999;",
			attrs: {
				"data-identifier": "emoji-picker",
				role: "dialog"
			}
		});
		const innerContent = createEl("div", { className: "fk-d-menu__inner-content" });
		const emojiPickerDiv = createEl("div", { className: "emoji-picker" });
		const filterContainer = createEl("div", { className: "emoji-picker__filter-container" });
		const filterDiv = createEl("div", { className: "emoji-picker__filter filter-input-container" });
		const searchInput = createEl("input", {
			className: "filter-input",
			placeholder: "按表情符号名称搜索…",
			type: "text"
		});
		filterDiv.appendChild(searchInput);
		filterContainer.appendChild(filterDiv);
		const content = createEl("div", { className: "emoji-picker__content" });
		const sectionsNav = createEl("div", { className: "emoji-picker__sections-nav" });
		const managementButton = createEl("button", {
			className: "btn no-text btn-flat emoji-picker__section-btn management-btn",
			attrs: {
				tabindex: "-1",
				style: "border-right: 1px solid #ddd;"
			},
			type: "button",
			innerHTML: "⚙️",
			title: "管理表情 - 点击打开完整管理界面"
		});
		managementButton.addEventListener("click", () => {
			__vitePreload(async () => {
				const { openManagementInterface: openManagementInterface$1 } = await Promise.resolve().then(() => (init_manager(), manager_exports));
				return { openManagementInterface: openManagementInterface$1 };
			}, void 0).then(({ openManagementInterface: openManagementInterface$1 }) => {
				openManagementInterface$1();
			});
		});
		sectionsNav.appendChild(managementButton);
		const settingsButton = createEl("button", {
			className: "btn no-text btn-flat emoji-picker__section-btn settings-btn",
			attrs: {
				tabindex: "-1",
				style: "border-right: 1px solid #ddd;"
			},
			type: "button",
			innerHTML: "🔧",
			title: "设置"
		});
		settingsButton.addEventListener("click", () => {
			__vitePreload(async () => {
				const { showSettingsModal: showSettingsModal$1 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
				return { showSettingsModal: showSettingsModal$1 };
			}, void 0).then(({ showSettingsModal: showSettingsModal$1 }) => {
				showSettingsModal$1();
			});
		});
		sectionsNav.appendChild(settingsButton);
		const scrollableContent = createEl("div", { className: "emoji-picker__scrollable-content" });
		const sections = createEl("div", {
			className: "emoji-picker__sections",
			attrs: { role: "button" }
		});
		groups.forEach((group, index) => {
			if (!group?.emojis?.length) return;
			const navButton = createEl("button", {
				className: `btn no-text btn-flat emoji-picker__section-btn ${index === 0 ? "active" : ""}`,
				attrs: {
					tabindex: "-1",
					"data-section": group.id
				},
				type: "button"
			});
			const iconVal = group.icon || "📁";
			if (isImageUrl(iconVal)) {
				const img = createEl("img", {
					src: iconVal,
					alt: group.name || "",
					className: "emoji-group-icon",
					style: "width: 18px; height: 18px; object-fit: contain;"
				});
				navButton.appendChild(img);
			} else navButton.textContent = String(iconVal);
			navButton.title = group.name;
			navButton.addEventListener("click", () => {
				sectionsNav.querySelectorAll(".emoji-picker__section-btn").forEach((btn) => btn.classList.remove("active"));
				navButton.classList.add("active");
				const target = sections.querySelector(`[data-section="${group.id}"]`);
				if (target) target.scrollIntoView({
					behavior: "smooth",
					block: "start"
				});
			});
			sectionsNav.appendChild(navButton);
			const section = createEl("div", {
				className: "emoji-picker__section",
				attrs: {
					"data-section": group.id,
					role: "region",
					"aria-label": group.name
				}
			});
			const titleContainer = createEl("div", { className: "emoji-picker__section-title-container" });
			const title = createEl("h2", {
				className: "emoji-picker__section-title",
				text: group.name
			});
			titleContainer.appendChild(title);
			const sectionEmojis = createEl("div", { className: "emoji-picker__section-emojis" });
			let added = 0;
			group.emojis.forEach((emoji) => {
				if (!emoji || typeof emoji !== "object" || !emoji.url || !emoji.name) return;
				const img = createEl("img", {
					width: "32px",
					height: "32px",
					className: "emoji",
					src: emoji.url,
					alt: emoji.name,
					title: `:${emoji.name}:`,
					attrs: {
						"data-emoji": emoji.name,
						tabindex: "0",
						loading: "lazy"
					}
				});
				(function bindHover(imgEl, emo) {
					if (!userscriptState.settings?.enableFloatingPreview) return;
					const preview = ensureHoverPreview();
					const previewImg = preview.querySelector("img");
					const previewLabel = preview.querySelector(".emoji-picker-hover-label");
					let fadeTimer = null;
					function onEnter(e) {
						previewImg.src = emo.url;
						previewLabel.textContent = emo.name || "";
						preview.style.display = "block";
						preview.style.opacity = "1";
						preview.style.transition = "opacity 0.12s ease, transform 0.12s ease";
						if (fadeTimer) {
							clearTimeout(fadeTimer);
							fadeTimer = null;
						}
						fadeTimer = window.setTimeout(() => {
							preview.style.opacity = "0";
							setTimeout(() => {
								if (preview.style.opacity === "0") preview.style.display = "none";
							}, 300);
						}, 5e3);
						move(e);
					}
					function move(e) {
						const pad = 12;
						const vw = window.innerWidth;
						const vh = window.innerHeight;
						const rect = preview.getBoundingClientRect();
						let left = e.clientX + pad;
						let top = e.clientY + pad;
						if (left + rect.width > vw) left = e.clientX - rect.width - pad;
						if (top + rect.height > vh) top = e.clientY - rect.height - pad;
						preview.style.left = left + "px";
						preview.style.top = top + "px";
					}
					function onLeave() {
						if (fadeTimer) {
							clearTimeout(fadeTimer);
							fadeTimer = null;
						}
						preview.style.display = "none";
					}
					imgEl.addEventListener("mouseenter", onEnter);
					imgEl.addEventListener("mousemove", move);
					imgEl.addEventListener("mouseleave", onLeave);
				})(img, emoji);
				img.addEventListener("click", () => {
					insertEmojiIntoEditor(emoji);
					picker.remove();
				});
				img.addEventListener("keydown", (e) => {
					if (e.key === "Enter" || e.key === " ") {
						e.preventDefault();
						insertEmojiIntoEditor(emoji);
						picker.remove();
					}
				});
				sectionEmojis.appendChild(img);
				added++;
			});
			if (added === 0) {
				const msg = createEl("div", {
					text: `${group.name} 组暂无有效表情`,
					style: "padding: 20px; text-align: center; color: #999;"
				});
				sectionEmojis.appendChild(msg);
			}
			section.appendChild(titleContainer);
			section.appendChild(sectionEmojis);
			sections.appendChild(section);
		});
		searchInput.addEventListener("input", (e) => {
			const q = (e.target.value || "").toLowerCase();
			sections.querySelectorAll("img").forEach((img) => {
				const emojiName = img.getAttribute("data-emoji")?.toLowerCase() || "";
				img.style.display = q === "" || emojiName.includes(q) ? "" : "none";
			});
			sections.querySelectorAll(".emoji-picker__section").forEach((section) => {
				const visibleEmojis = section.querySelectorAll("img:not([style*=\"none\"])");
				const titleContainer = section.querySelector(".emoji-picker__section-title-container");
				if (titleContainer) titleContainer.style.display = visibleEmojis.length > 0 ? "" : "none";
			});
		});
		scrollableContent.appendChild(sections);
		content.appendChild(sectionsNav);
		content.appendChild(scrollableContent);
		emojiPickerDiv.appendChild(filterContainer);
		emojiPickerDiv.appendChild(content);
		innerContent.appendChild(emojiPickerDiv);
		picker.appendChild(innerContent);
		return picker;
	}
	async function createEmojiPicker() {
		const groups = userscriptState.emojiGroups;
		const mobile = isMobileView();
		try {
			injectEmojiPickerStyles();
		} catch (e) {
			console.warn("injectEmojiPickerStyles failed", e);
		}
		if (mobile) return createMobileEmojiPicker(groups);
		else return createDesktopEmojiPicker(groups);
	}
	init_createEl();
	init_popularEmojis();
	var QUICK_INSERTS = [
		"info",
		"tip",
		"faq",
		"question",
		"note",
		"abstract",
		"todo",
		"success",
		"warning",
		"failure",
		"danger",
		"bug",
		"example",
		"quote"
	];
	var ICON_MAP = {
		info: "ℹ️",
		tip: "💡",
		faq: "❓",
		question: "🤔",
		note: "📝",
		abstract: "📋",
		todo: "☑️",
		success: "🎉",
		warning: "⚠️",
		failure: "❌",
		danger: "☠️",
		bug: "🐛",
		example: "🔎",
		quote: "💬"
	};
	function insertIntoEditor(text) {
		const active = document.activeElement;
		const isTextarea = (el) => !!el && el.tagName === "TEXTAREA";
		if (isTextarea(active)) {
			const textarea = active;
			const start = textarea.selectionStart ?? 0;
			const end = textarea.selectionEnd ?? start;
			const value = textarea.value;
			textarea.value = value.slice(0, start) + text + value.slice(end);
			const pos = start + text.length;
			if ("setSelectionRange" in textarea) try {
				textarea.setSelectionRange(pos, pos);
			} catch (e) {}
			textarea.dispatchEvent(new Event("input", { bubbles: true }));
			return;
		}
		if (active && active.isContentEditable) {
			const sel = window.getSelection();
			if (!sel) return;
			const range = sel.getRangeAt(0);
			range.deleteContents();
			const node = document.createTextNode(text);
			range.insertNode(node);
			range.setStartAfter(node);
			range.setEndAfter(node);
			sel.removeAllRanges();
			sel.addRange(range);
			active.dispatchEvent(new Event("input", { bubbles: true }));
			return;
		}
		const fallback = document.querySelector("textarea");
		if (fallback) {
			fallback.focus();
			const start = fallback.selectionStart ?? fallback.value.length;
			const end = fallback.selectionEnd ?? start;
			const value = fallback.value;
			fallback.value = value.slice(0, start) + text + value.slice(end);
			const pos = start + text.length;
			if ("setSelectionRange" in fallback) try {
				fallback.setSelectionRange(pos, pos);
			} catch (e) {}
			fallback.dispatchEvent(new Event("input", { bubbles: true }));
		}
	}
	function createQuickInsertMenu() {
		const menu = document.createElement("div");
		menu.className = "fk-d-menu toolbar-menu__options-content toolbar-popup-menu-options -animated -expanded";
		const inner = document.createElement("div");
		inner.className = "fk-d-menu__inner-content";
		const list = document.createElement("ul");
		list.className = "dropdown-menu";
		QUICK_INSERTS.forEach((key) => {
			const li = document.createElement("li");
			li.className = "dropdown-menu__item";
			const btn = document.createElement("button");
			btn.className = "btn btn-icon-text";
			btn.type = "button";
			const displayLabel = key.charAt(0).toUpperCase() + key.slice(1);
			btn.title = displayLabel;
			btn.addEventListener("click", () => {
				if (menu.parentElement) menu.parentElement.removeChild(menu);
				insertIntoEditor(`>[!${key}]`);
			});
			const emojiSpan = document.createElement("span");
			emojiSpan.textContent = ICON_MAP[key] || "✳️";
			const labelWrap = document.createElement("span");
			labelWrap.className = "d-button-label";
			const labelText = document.createElement("span");
			labelText.className = "d-button-label__text";
			labelText.textContent = displayLabel;
			labelWrap.appendChild(labelText);
			btn.appendChild(emojiSpan);
			btn.appendChild(labelWrap);
			li.appendChild(btn);
			list.appendChild(li);
		});
		inner.appendChild(list);
		menu.appendChild(inner);
		return menu;
	}
	function findAllToolbars() {
		const toolbars = [];
		const selectors = getPlatformToolbarSelectors();
		for (const selector of selectors) {
			const elements = document.querySelectorAll(selector);
			toolbars.push(...Array.from(elements));
		}
		return toolbars;
	}
	var currentPicker = null;
	function closeCurrentPicker() {
		if (currentPicker) {
			currentPicker.remove();
			currentPicker = null;
		}
	}
	function injectEmojiButton(toolbar) {
		if (toolbar.querySelector(".emoji-extension-button")) return;
		const isChatComposer = toolbar.classList.contains("chat-composer__inner-container");
		const button = createEl("button", {
			className: "btn no-text btn-icon toolbar__button nacho-emoji-picker-button emoji-extension-button",
			title: "表情包",
			type: "button",
			innerHTML: "🐈‍⬛"
		});
		const popularButton = createEl("button", {
			className: "btn no-text btn-icon toolbar__button nacho-emoji-popular-button emoji-extension-button",
			title: "常用表情",
			type: "button",
			innerHTML: "⭐"
		});
		if (isChatComposer) {
			button.classList.add("fk-d-menu__trigger", "emoji-picker-trigger", "chat-composer-button", "btn-transparent", "-emoji");
			button.setAttribute("aria-expanded", "false");
			button.setAttribute("data-identifier", "emoji-picker");
			button.setAttribute("data-trigger", "");
			popularButton.classList.add("fk-d-menu__trigger", "popular-emoji-trigger", "chat-composer-button", "btn-transparent", "-popular");
			popularButton.setAttribute("aria-expanded", "false");
			popularButton.setAttribute("data-identifier", "popular-emoji");
			popularButton.setAttribute("data-trigger", "");
		}
		button.addEventListener("click", async (e) => {
			e.stopPropagation();
			if (currentPicker) {
				closeCurrentPicker();
				return;
			}
			currentPicker = await createEmojiPicker();
			if (!currentPicker) return;
			document.body.appendChild(currentPicker);
			const buttonRect = button.getBoundingClientRect();
			if (currentPicker.classList.contains("modal") || currentPicker.className.includes("d-modal")) {
				currentPicker.style.position = "fixed";
				currentPicker.style.top = "0";
				currentPicker.style.left = "0";
				currentPicker.style.right = "0";
				currentPicker.style.bottom = "0";
				currentPicker.style.zIndex = "999999";
			} else {
				currentPicker.style.position = "fixed";
				const margin = 8;
				const vpWidth = window.innerWidth;
				const vpHeight = window.innerHeight;
				currentPicker.style.top = buttonRect.bottom + margin + "px";
				currentPicker.style.left = buttonRect.left + "px";
				const pickerRect = currentPicker.getBoundingClientRect();
				const spaceBelow = vpHeight - buttonRect.bottom;
				const neededHeight = pickerRect.height + margin;
				let top = buttonRect.bottom + margin;
				if (spaceBelow < neededHeight) top = Math.max(margin, buttonRect.top - pickerRect.height - margin);
				let left = buttonRect.left;
				if (left + pickerRect.width + margin > vpWidth) left = Math.max(margin, vpWidth - pickerRect.width - margin);
				if (left < margin) left = margin;
				currentPicker.style.top = top + "px";
				currentPicker.style.left = left + "px";
			}
			setTimeout(() => {
				const handleClick = (e$1) => {
					if (currentPicker && !currentPicker.contains(e$1.target) && e$1.target !== button) {
						closeCurrentPicker();
						document.removeEventListener("click", handleClick);
					}
				};
				document.addEventListener("click", handleClick);
			}, 100);
		});
		popularButton.addEventListener("click", (e) => {
			e.stopPropagation();
			closeCurrentPicker();
			showPopularEmojisModal();
		});
		const quickInsertButton = createEl("button", {
			className: "btn no-text btn-icon toolbar__button quick-insert-button",
			title: "快捷输入",
			type: "button",
			innerHTML: "⎘"
		});
		if (isChatComposer) {
			quickInsertButton.classList.add("fk-d-menu__trigger", "chat-composer-button", "btn-transparent");
			quickInsertButton.setAttribute("aria-expanded", "false");
			quickInsertButton.setAttribute("data-trigger", "");
		}
		quickInsertButton.addEventListener("click", (e) => {
			e.stopPropagation();
			const menu = createQuickInsertMenu();
			(document.querySelector("#d-menu-portals") || document.body).appendChild(menu);
			const rect = quickInsertButton.getBoundingClientRect();
			menu.style.position = "fixed";
			menu.style.zIndex = "10000";
			menu.style.top = `${rect.bottom + 5}px`;
			menu.style.left = `${Math.max(8, Math.min(rect.left + rect.width / 2 - 150, window.innerWidth - 300))}px`;
			const removeMenu = (ev) => {
				if (!menu.contains(ev.target)) {
					if (menu.parentElement) menu.parentElement.removeChild(menu);
					document.removeEventListener("click", removeMenu);
				}
			};
			setTimeout(() => document.addEventListener("click", removeMenu), 100);
		});
		try {
			if (isChatComposer) {
				const existingEmojiTrigger = toolbar.querySelector(".emoji-picker-trigger:not(.emoji-extension-button)");
				if (existingEmojiTrigger) {
					toolbar.insertBefore(button, existingEmojiTrigger);
					toolbar.insertBefore(quickInsertButton, existingEmojiTrigger);
					toolbar.insertBefore(popularButton, existingEmojiTrigger);
				} else {
					toolbar.appendChild(button);
					toolbar.appendChild(quickInsertButton);
					toolbar.appendChild(popularButton);
				}
			} else {
				toolbar.appendChild(button);
				toolbar.appendChild(quickInsertButton);
				toolbar.appendChild(popularButton);
			}
		} catch (error) {
			console.error("[Emoji Extension Userscript] Failed to inject button:", error);
		}
	}
	function attemptInjection() {
		const toolbars = findAllToolbars();
		let injectedCount = 0;
		toolbars.forEach((toolbar) => {
			if (!toolbar.querySelector(".emoji-extension-button")) {
				console.log("[Emoji Extension Userscript] Toolbar found, injecting button.");
				injectEmojiButton(toolbar);
				injectedCount++;
			}
		});
		return {
			injectedCount,
			totalToolbars: toolbars.length
		};
	}
	function startPeriodicInjection() {
		setInterval(() => {
			findAllToolbars().forEach((toolbar) => {
				if (!toolbar.querySelector(".emoji-extension-button")) {
					console.log("[Emoji Extension Userscript] New toolbar found, injecting button.");
					injectEmojiButton(toolbar);
				}
			});
		}, 3e4);
	}
	init_createEl();
	init_themeSupport();
	var floatingButton = null;
	var isButtonVisible = false;
	var FLOATING_BUTTON_STYLES = `
.emoji-extension-floating-button {
  position: fixed !important;
  bottom: 20px !important;
  right: 20px !important;
  width: 56px !important;
  height: 56px !important;
  border-radius: 50% !important;
  background: linear-gradient(135deg, var(--emoji-button-gradient-start) 0%, var(--emoji-button-gradient-end) 100%) !important;
  border: none !important;
  box-shadow: 0 4px 12px var(--emoji-button-shadow) !important;
  cursor: pointer !important;
  z-index: 999999 !important;
  font-size: 24px !important;
  color: white !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  transition: all 0.3s ease !important;
  opacity: 0.9 !important;
  line-height: 1 !important;
}

.emoji-extension-floating-button:hover {
  transform: scale(1.1) !important;
  opacity: 1 !important;
  box-shadow: 0 6px 16px var(--emoji-button-hover-shadow) !important;
}

.emoji-extension-floating-button:active {
  transform: scale(0.95) !important;
}

.emoji-extension-floating-button.hidden {
  opacity: 0 !important;
  pointer-events: none !important;
  transform: translateY(20px) !important;
}

@media (max-width: 768px) {
  .emoji-extension-floating-button {
    bottom: 15px !important;
    right: 15px !important;
    width: 48px !important;
    height: 48px !important;
    font-size: 20px !important;
  }
}
`;
	function injectStyles() {
		if (document.getElementById("emoji-extension-floating-button-styles")) return;
		injectGlobalThemeStyles();
		const style = createEl("style", {
			attrs: { id: "emoji-extension-floating-button-styles" },
			text: FLOATING_BUTTON_STYLES
		});
		document.head.appendChild(style);
	}
	function createFloatingButton() {
		const button = createEl("button", {
			className: "emoji-extension-floating-button",
			title: "手动注入表情按钮 (Manual Emoji Injection)",
			innerHTML: "🐈‍⬛"
		});
		button.addEventListener("click", async (e) => {
			e.stopPropagation();
			e.preventDefault();
			button.style.transform = "scale(0.9)";
			button.innerHTML = "⏳";
			try {
				const result = attemptInjection();
				if (result.injectedCount > 0) {
					button.innerHTML = "✅";
					button.style.background = "linear-gradient(135deg, #56ab2f 0%, #a8e6cf 100%)";
					setTimeout(() => {
						button.innerHTML = "🐈‍⬛";
						button.style.background = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)";
						button.style.transform = "scale(1)";
					}, 1500);
					console.log(`[Emoji Extension Userscript] Manual injection successful: ${result.injectedCount} buttons injected into ${result.totalToolbars} toolbars`);
				} else {
					button.innerHTML = "❌";
					button.style.background = "linear-gradient(135deg, #ff6b6b 0%, #ffa8a8 100%)";
					setTimeout(() => {
						button.innerHTML = "🐈‍⬛";
						button.style.background = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)";
						button.style.transform = "scale(1)";
					}, 1500);
					console.log("[Emoji Extension Userscript] Manual injection failed: No compatible toolbars found");
				}
			} catch (error) {
				button.innerHTML = "⚠️";
				button.style.background = "linear-gradient(135deg, #ff6b6b 0%, #ffa8a8 100%)";
				setTimeout(() => {
					button.innerHTML = "🐈‍⬛";
					button.style.background = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)";
					button.style.transform = "scale(1)";
				}, 1500);
				console.error("[Emoji Extension Userscript] Manual injection error:", error);
			}
		});
		return button;
	}
	function showFloatingButton() {
		if (floatingButton) return;
		injectStyles();
		floatingButton = createFloatingButton();
		document.body.appendChild(floatingButton);
		isButtonVisible = true;
		console.log("[Emoji Extension Userscript] Floating manual injection button shown");
	}
	function hideFloatingButton() {
		if (floatingButton) {
			floatingButton.classList.add("hidden");
			setTimeout(() => {
				if (floatingButton) {
					floatingButton.remove();
					floatingButton = null;
					isButtonVisible = false;
				}
			}, 300);
			console.log("[Emoji Extension Userscript] Floating manual injection button hidden");
		}
	}
	function autoShowFloatingButton() {
		if (!isButtonVisible) {
			console.log("[Emoji Extension Userscript] Auto-showing floating button due to injection difficulties");
			showFloatingButton();
		}
	}
	function checkAndShowFloatingButton() {
		const existingButtons = document.querySelectorAll(".emoji-extension-button");
		if (existingButtons.length === 0 && !isButtonVisible) setTimeout(() => {
			autoShowFloatingButton();
		}, 2e3);
		else if (existingButtons.length > 0 && isButtonVisible) hideFloatingButton();
	}
	init_userscript_storage();
	init_state();
	async function initializeUserscriptData() {
		const data = await loadDataFromLocalStorageAsync().catch((err) => {
			console.warn("[Userscript] loadDataFromLocalStorageAsync failed, falling back to sync loader", err);
			return loadDataFromLocalStorage();
		});
		userscriptState.emojiGroups = data.emojiGroups || [];
		userscriptState.settings = data.settings || userscriptState.settings;
	}
	function shouldInjectEmoji() {
		if (document.querySelectorAll("meta[name*=\"discourse\"], meta[content*=\"discourse\"], meta[property*=\"discourse\"]").length > 0) {
			console.log("[Emoji Extension Userscript] Discourse detected via meta tags");
			return true;
		}
		const generatorMeta = document.querySelector("meta[name=\"generator\"]");
		if (generatorMeta) {
			const content = generatorMeta.getAttribute("content")?.toLowerCase() || "";
			if (content.includes("discourse") || content.includes("flarum") || content.includes("phpbb")) {
				console.log("[Emoji Extension Userscript] Forum platform detected via generator meta");
				return true;
			}
		}
		const hostname = window.location.hostname.toLowerCase();
		if ([
			"linux.do",
			"meta.discourse.org",
			"pixiv.net"
		].some((domain) => hostname.includes(domain))) {
			console.log("[Emoji Extension Userscript] Allowed domain detected:", hostname);
			return true;
		}
		if (document.querySelectorAll("textarea.d-editor-input, .ProseMirror.d-editor-input, .composer-input, .reply-area textarea").length > 0) {
			console.log("[Emoji Extension Userscript] Discussion editor detected");
			return true;
		}
		console.log("[Emoji Extension Userscript] No compatible platform detected");
		return false;
	}
	async function initializeEmojiFeature(maxAttempts = 10, delay = 1e3) {
		console.log("[Emoji Extension Userscript] Initializing...");
		logPlatformInfo();
		await initializeUserscriptData();
		initOneClickAdd();
		let attempts = 0;
		function attemptToolbarInjection() {
			attempts++;
			const result = attemptInjection();
			if (result.injectedCount > 0 || result.totalToolbars > 0) {
				console.log(`[Emoji Extension Userscript] Injection successful: ${result.injectedCount} buttons injected into ${result.totalToolbars} toolbars`);
				return;
			}
			if (attempts < maxAttempts) {
				console.log(`[Emoji Extension Userscript] Toolbar not found, attempt ${attempts}/${maxAttempts}. Retrying in ${delay / 1e3}s.`);
				setTimeout(attemptToolbarInjection, delay);
			} else {
				console.error("[Emoji Extension Userscript] Failed to find toolbar after multiple attempts.");
				console.log("[Emoji Extension Userscript] Showing floating button as fallback");
				showFloatingButton();
			}
		}
		if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", attemptToolbarInjection);
		else attemptToolbarInjection();
		startPeriodicInjection();
		setInterval(() => {
			checkAndShowFloatingButton();
		}, 5e3);
	}
	if (shouldInjectEmoji()) {
		console.log("[Emoji Extension Userscript] Initializing emoji feature");
		initializeEmojiFeature();
	} else console.log("[Emoji Extension Userscript] Skipping injection - incompatible platform");
})();

})();