KG_Chat_Application

Enhance the chat abilities

目前为 2025-04-03 提交的版本。查看 最新版本

// ==UserScript==
// @name         KG_Chat_Application
// @namespace    klavogonki
// @version      3.0.1
// @description  Enhance the chat abilities
// @author       Patcher
// @match        *://klavogonki.ru/*
// @exclude      https://klavogonki.ru/g/?gmid=*
// @exclude      https://klavogonki.ru/chatlogs/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=klavogonki.ru
// @grant        GM_xmlhttpRequest
// ==/UserScript==

/*
 * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
 * This devtool is neither made for production nor for readable output files.
 * It uses "eval()" calls to create a separate source file in the browser devtools.
 * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
 * or disable the default devtool with "devtool: false".
 * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
 */
/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	var __webpack_modules__ = ({

/***/ "./main.js":
/*!*****************!*\
  !*** ./main.js ***!
  \*****************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _src_data_definitions_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./src/data/definitions.js */ \"./src/data/definitions.js\");\n/* harmony import */ var _src_xmpp_xmppConnection_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./src/xmpp/xmppConnection.js */ \"./src/xmpp/xmppConnection.js\");\n/* harmony import */ var _src_managers_userManager_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./src/managers/userManager.js */ \"./src/managers/userManager.js\");\n/* harmony import */ var _src_managers_messageManager_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./src/managers/messageManager.js */ \"./src/managers/messageManager.js\");\n/* harmony import */ var _src_chat_chatUI_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./src/chat/chatUI.js */ \"./src/chat/chatUI.js\");\n/* harmony import */ var _src_auth_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./src/auth.js */ \"./src/auth.js\");\n/* harmony import */ var _src_chat_chatEvents_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./src/chat/chatEvents.js */ \"./src/chat/chatEvents.js\");\n/* harmony import */ var _src_xmpp_xmppClient_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./src/xmpp/xmppClient.js */ \"./src/xmpp/xmppClient.js\");\n/* harmony import */ var _src_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./src/helpers/helpers.js */ \"./src/helpers/helpers.js\");\n/* harmony import */ var _src_components_helpPanel_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./src/components/helpPanel.js */ \"./src/components/helpPanel.js\");\n/* harmony import */ var _src_components_updateCheck_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./src/components/updateCheck.js */ \"./src/components/updateCheck.js\");\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n// Function to detect if running in an iframe\r\nfunction isInIframe() {\r\n  try {\r\n    return window !== window.top;\r\n  } catch (e) {\r\n    // If there's an error when trying to access window.top, \r\n    // it's likely due to cross-origin restrictions, which means we're in an iframe\r\n    return true;\r\n  }\r\n}\r\n\r\n// ------------------------- Auth Check ---------------------------\r\nfunction checkAuth() {\r\n  // First check if running in iframe\r\n  if (isInIframe()) {\r\n    console.error('Application cannot run in an iframe');\r\n    return false;\r\n  }\r\n\r\n  const params = new URLSearchParams(window.location.search);\r\n  if (window.location.pathname === '/g/' && params.has('gmid')) {\r\n    return false;\r\n  }\r\n  if (window.location.href.includes('/gamelist/')) {\r\n    (0,_src_auth_js__WEBPACK_IMPORTED_MODULE_5__.getAuthData)();\r\n    return false;\r\n  }\r\n  const authData = localStorage.getItem('klavoauth');\r\n  if (!authData || !_src_auth_js__WEBPACK_IMPORTED_MODULE_5__.klavoauth.username || !_src_auth_js__WEBPACK_IMPORTED_MODULE_5__.klavoauth.password) {\r\n    localStorage.removeItem('klavoauth');\r\n    window.location.href = 'https://klavogonki.ru/gamelist/';\r\n    return false;\r\n  }\r\n  return true;\r\n}\r\n\r\n// ------------------------- Main App ---------------------------\r\nasync function initializeApp() {\r\n  try {\r\n    // Add viewport meta tag\r\n    (0,_src_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_8__.addViewportMeta)();\r\n\r\n    if (!checkAuth()) return;\r\n\r\n    // Initialize UI and features\r\n    (0,_src_chat_chatUI_js__WEBPACK_IMPORTED_MODULE_4__.createChatUI)();\r\n    (0,_src_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_8__.addChatToggleFeature)();\r\n    (0,_src_chat_chatEvents_js__WEBPACK_IMPORTED_MODULE_6__.setupDragHandlers)();\r\n    (0,_src_chat_chatEvents_js__WEBPACK_IMPORTED_MODULE_6__.setupResizeHandlers)();\r\n    (0,_src_chat_chatEvents_js__WEBPACK_IMPORTED_MODULE_6__.setupWindowResizeHandler)();\r\n\r\n    // Set up the messages panel observer\r\n    (0,_src_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_8__.observeMessagesPanel)();\r\n\r\n    // Initialize managers and XMPP connection\r\n    const userManager = new _src_managers_userManager_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"]('user-list');\r\n    const messageManager = new _src_managers_messageManager_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"]('messages-panel', (0,_src_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_8__.parseUsername)(_src_auth_js__WEBPACK_IMPORTED_MODULE_5__.klavoauth.username));\r\n    const xmppConnection = new _src_xmpp_xmppConnection_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]({\r\n      username: _src_auth_js__WEBPACK_IMPORTED_MODULE_5__.klavoauth.username,\r\n      password: _src_auth_js__WEBPACK_IMPORTED_MODULE_5__.klavoauth.password,\r\n      bindUrl: _src_data_definitions_js__WEBPACK_IMPORTED_MODULE_0__.XMPP_BIND_URL,\r\n      connectionDelay: _src_data_definitions_js__WEBPACK_IMPORTED_MODULE_0__.connectionDelay\r\n    });\r\n\r\n    const xmppClient = (0,_src_xmpp_xmppClient_js__WEBPACK_IMPORTED_MODULE_7__.createXMPPClient)(\r\n      xmppConnection,\r\n      userManager,\r\n      messageManager,\r\n      _src_auth_js__WEBPACK_IMPORTED_MODULE_5__.klavoauth.username\r\n    );\r\n\r\n    const input = document.getElementById('message-input');\r\n\r\n    const sendMessage = () => {\r\n      const text = input.value.trim();\r\n      if (!text) return;\r\n      xmppClient.sendMessage(text);\r\n      input.value = '';\r\n      input.focus();\r\n    };\r\n\r\n    // Set up event listeners\r\n    document.getElementById('send-button').addEventListener('click', sendMessage);\r\n    input.addEventListener('keypress', e => e.key === 'Enter' && sendMessage());\r\n    input.addEventListener('paste', e => requestAnimationFrame(() => input.value = (0,_src_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_8__.decodeEncodedURL)(input.value)));\r\n\r\n    // Set up private messaging events\r\n    (0,_src_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_8__.setupPrivateMessageEvents)(input);\r\n    // Set up reset command event\r\n    (0,_src_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_8__.setupCommandEvents)(input);\r\n    // New: Set up help command events (similar to /pm command)\r\n    _src_components_helpPanel_js__WEBPACK_IMPORTED_MODULE_9__.HelpPanel.setupHelpCommandEvents();\r\n\r\n    // Connect to XMPP and join the room\r\n    await xmppClient.connect();\r\n    window.xmppClient = xmppClient; // For debugging\r\n\r\n  } catch (error) {\r\n    console.error('App init error:', error);\r\n    (0,_src_auth_js__WEBPACK_IMPORTED_MODULE_5__.removeChatParams)();\r\n  }\r\n}\r\n\r\n// Start the app and check for updates\r\ninitializeApp().then(() => {\r\n  (0,_src_components_updateCheck_js__WEBPACK_IMPORTED_MODULE_10__.checkForUpdates)();\r\n});\n\n//# sourceURL=webpack://tampermonkey-script/./main.js?");

/***/ }),

/***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/chatUsernameColors.scss":
/*!*************************************************************************************************************************!*\
  !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/chatUsernameColors.scss ***!
  \*************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports\n\n\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, `.chat-username-color-picker {\n  opacity: 0;\n  position: fixed;\n  left: 50%;\n  top: 50%;\n  transform: translate(-50%, -50%);\n  height: fit-content;\n  max-height: 70vh !important;\n  max-width: 360px !important;\n  width: fit-content;\n  overflow-y: auto;\n  scrollbar-width: none;\n  background: #1e1e1e;\n  border: 1px solid #333 !important;\n  padding: 1em;\n  border-radius: 0.4em !important;\n  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;\n  font-family: \"Gill Sans\", \"Gill Sans MT\", Calibri, \"Trebuchet MS\", sans-serif;\n  z-index: 1100;\n}\n.chat-username-color-picker h2 {\n  font-size: 1.2em !important;\n  margin: 0 !important;\n  text-align: center !important;\n  color: #ffa500 !important;\n  border-bottom: 1px dashed #444 !important;\n  padding-bottom: 0.6em !important;\n}\n.chat-username-color-picker .saved-username-colors,\n.chat-username-color-picker .generated-username-colors {\n  margin-top: 1em;\n}\n.chat-username-color-picker .saved-username-colors h3,\n.chat-username-color-picker .generated-username-colors h3 {\n  font-size: 1em !important;\n  margin: 0.5em 0 !important;\n  text-align: center !important;\n  color: #00ff58 !important;\n  border-bottom: 1px dashed #444 !important;\n  padding-bottom: 0.3em !important;\n}\n.chat-username-color-picker .saved-username-colors h3 .counter,\n.chat-username-color-picker .generated-username-colors h3 .counter {\n  font-size: 0.8em !important;\n  color: #ffa500 !important;\n  margin-left: 0.5em !important;\n}\n.chat-username-color-picker .generated-username-colors .username-entry.disabled-entry {\n  pointer-events: none;\n  opacity: 0.2;\n}\n.chat-username-color-picker .username-entry {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 0.3em;\n  border-bottom: 1px dashed #444 !important;\n}\n.chat-username-color-picker .username-entry:last-child {\n  border-bottom: none;\n}\n.chat-username-color-picker .username-entry .user-label {\n  flex: 1;\n}\n.chat-username-color-picker .username-entry .color-box {\n  cursor: pointer !important;\n  border-radius: 0.2em !important;\n  margin-left: 1em;\n  padding: 0.6em;\n  font-size: 0.8em;\n  font-family: monospace;\n}\n.chat-username-color-picker .username-entry input[type=color] {\n  display: none !important;\n}\n.chat-username-color-picker .remove-btn {\n  cursor: pointer;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 25px;\n  height: 25px;\n  border-radius: 0.2em !important;\n  margin-left: 8px;\n}\n.chat-username-color-picker .custom-color-input {\n  display: flex;\n  align-items: center;\n  background-color: #1e1e1e !important;\n  position: absolute;\n  left: 0 !important;\n  top: 0 !important;\n  right: 0 !important;\n}\n.chat-username-color-picker .custom-color-input .hex-input {\n  padding: 0.6em !important;\n  border-radius: 0.4em 0 0 0 !important;\n  background-color: #111111 !important;\n  color: #cdb398 !important;\n  caret-color: #cdb398 !important;\n  width: 100% !important;\n  border: none !important;\n  outline: none !important;\n}\n.chat-username-color-picker .custom-color-input .confirm-btn {\n  transition: background-color 0.3s ease !important;\n  padding: 0.6em !important;\n  background-color: #2E6C30 !important;\n  color: lightgreen !important;\n  border: none !important;\n  outline: none !important;\n  border-radius: 0 0.4em 0 0 !important;\n  cursor: pointer;\n}\n.chat-username-color-picker .custom-color-input .confirm-btn:hover {\n  background-color: #38833c !important;\n}`, \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/styles/chatUsernameColors.scss?./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js");

/***/ }),

/***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/emojiPanel.scss":
/*!*****************************************************************************************************************!*\
  !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/emojiPanel.scss ***!
  \*****************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports\n\n\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, `.emoji-panel {\n  opacity: 0;\n  transition: opacity 0.3s ease;\n  position: absolute !important;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  background: #1e1e1e !important;\n  border: 1px solid #333 !important;\n  border-radius: 0.4em !important;\n  width: 380px;\n  height: 580px;\n  display: flex;\n  flex-direction: column;\n  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;\n  z-index: 1010;\n}\n.emoji-panel .emoji-search-container {\n  padding: 1em !important;\n  border: none !important;\n}\n.emoji-panel .emoji-search-container .emoji-search {\n  width: 100%;\n  padding: 8px;\n  border-radius: 4px;\n  background: #2a2a2a !important;\n  border: none !important;\n  border-radius: 0.2em !important;\n  color: #cdb398 !important;\n  caret-color: #cdb398 !important;\n  font-size: 0.9em !important;\n}\n.emoji-panel .emoji-search-container .emoji-search:focus {\n  outline: none;\n  border-color: #666;\n}\n.emoji-panel .emoji-categories {\n  position: sticky !important;\n  top: 0 !important;\n  display: grid !important;\n  grid-template-columns: repeat(auto-fill, minmax(32px, 1fr)) !important;\n  padding: 8px !important;\n  border-bottom: 1px solid #333 !important;\n  gap: 8px !important;\n  justify-content: center !important;\n  align-items: center !important;\n  overflow-x: auto !important;\n  scrollbar-width: thin !important;\n}\n.emoji-panel .emoji-categories .emoji-category-btn {\n  font-family: \"Noto Color Emoji\" !important;\n  position: relative !important;\n  background: none !important;\n  border: none !important;\n  padding: 4px !important;\n  cursor: pointer !important;\n  font-size: 1.5em !important;\n  transition: background-color 0.2s;\n  width: 100% !important;\n  height: 100% !important;\n  display: flex !important;\n  align-items: center !important;\n  justify-content: center !important;\n  aspect-ratio: 1 !important;\n  border-bottom: 3px solid transparent !important;\n}\n.emoji-panel .emoji-categories .emoji-category-btn.active {\n  opacity: 1;\n  border-bottom: 3px solid goldenrod !important;\n}\n.emoji-panel .emoji-categories .emoji-category-btn:hover {\n  background-color: #333;\n}\n.emoji-panel .emoji-categories::-webkit-scrollbar {\n  width: 6px;\n  height: 6px;\n}\n.emoji-panel .emoji-categories::-webkit-scrollbar-track {\n  background: #1e1e1e;\n}\n.emoji-panel .emoji-categories::-webkit-scrollbar-thumb {\n  background: #444;\n  border-radius: 3px;\n}\n.emoji-panel .emoji-categories::-webkit-scrollbar-thumb:hover {\n  background: #555;\n}\n.emoji-panel .emoji-container {\n  flex: 1;\n  overflow-y: auto;\n  overflow-x: hidden !important;\n  display: grid !important;\n  gap: 8px !important;\n  width: 100% !important;\n  max-width: 100% !important;\n  scrollbar-width: none !important;\n}\n.emoji-panel .emoji-container .emoji-category-section {\n  margin-bottom: 10px;\n}\n.emoji-panel .emoji-container .emoji-category-section .emoji-category-header {\n  padding: 8px !important;\n  color: #cdb398 !important;\n  font-size: 0.9em !important;\n  position: sticky !important;\n  top: 0px !important;\n  background: #1e1e1e !important;\n  z-index: 1 !important;\n  border-bottom: 1px solid #333 !important;\n  width: 100% !important;\n  box-sizing: border-box !important;\n  margin: 0 !important;\n}\n.emoji-panel .emoji-container .emoji-category-section .emoji-list {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(32px, 1fr)) !important;\n  padding: 8px !important;\n  gap: 8px;\n  align-content: start;\n}\n.emoji-panel .emoji-container .emoji-category-section .emoji-list .emoji-btn {\n  font-family: \"Noto Color Emoji\" !important;\n  background: none;\n  border: none;\n  padding: 4px !important;\n  cursor: pointer;\n  font-size: 1.5em !important;\n  transition: background-color 0.2s;\n  width: 100% !important;\n  height: 100% !important;\n  display: flex !important;\n  align-items: center !important;\n  justify-content: center !important;\n  aspect-ratio: 1 !important;\n}\n.emoji-panel .emoji-container .emoji-category-section .emoji-list .emoji-btn:hover {\n  background-color: #333;\n}\n.emoji-panel .emoji-container::-webkit-scrollbar {\n  width: 6px;\n  height: 6px;\n}\n.emoji-panel .emoji-container::-webkit-scrollbar-track {\n  background: #1e1e1e;\n}\n.emoji-panel .emoji-container::-webkit-scrollbar-thumb {\n  background: #444;\n  border-radius: 3px;\n}\n.emoji-panel .emoji-container::-webkit-scrollbar-thumb:hover {\n  background: #555;\n}\n.emoji-panel .emoji-footer {\n  border-top: 1px solid #333 !important;\n  display: flex;\n  flex-direction: row;\n  justify-content: space-between;\n  align-items: center;\n  padding: 5px;\n}\n.emoji-panel .emoji-footer .emoji-info-panel {\n  height: 40px !important;\n  padding: 8px !important;\n  display: flex !important;\n  align-items: center !important;\n  gap: 8px !important;\n  color: #cdb398 !important;\n  font-size: 0.9em !important;\n  background: #1e1e1e !important;\n}\n.emoji-panel .emoji-footer .emoji-info-panel .emoji-info-icon {\n  font-family: \"Noto Color Emoji\" !important;\n  font-size: 1.5em !important;\n}\n.emoji-panel .emoji-footer .emoji-info-panel .emoji-info-keywords {\n  color: #888 !important;\n  font-style: italic !important;\n}\n.emoji-panel .emoji-footer .emoji-language-select {\n  border-radius: 0.4em !important;\n  padding: 5px 10px;\n  font-size: 14px;\n  background-color: #2a2a2a !important;\n  border: 1px solid #444 !important;\n  color: #cdb398 !important;\n  cursor: pointer;\n  transition: border-color 0.3s ease;\n}\n.emoji-panel .emoji-footer .emoji-language-select:focus {\n  outline: none;\n  border-color: goldenrod !important;\n}\n.emoji-panel .emoji-footer .emoji-language-select:hover {\n  border-color: #666 !important;\n}\n.emoji-panel .emoji-footer .emoji-language-select option {\n  font-size: 14px;\n  background-color: #2a2a2a !important;\n  color: #cdb398 !important;\n}`, \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/styles/emojiPanel.scss?./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js");

/***/ }),

/***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/helpPanel.scss":
/*!****************************************************************************************************************!*\
  !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/helpPanel.scss ***!
  \****************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports\n\n\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, `.help-panel {\n  opacity: 0;\n  position: fixed;\n  left: 50%;\n  top: 50%;\n  transform: translate(-50%, -50%);\n  height: fit-content;\n  max-height: 70vh !important;\n  width: fit-content;\n  overflow-y: auto;\n  scrollbar-width: none;\n  background: #1e1e1e;\n  border: 1px solid #333 !important;\n  padding: 1em;\n  border-radius: 0.4em !important;\n  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;\n  color: #cdb398 !important;\n  font-family: \"Gill Sans\", \"Gill Sans MT\", Calibri, \"Trebuchet MS\", sans-serif;\n  z-index: 1100;\n}\n.help-panel .help-header {\n  margin-top: 0;\n  font-size: 1.5em;\n  text-align: center;\n  color: #ffa500 !important;\n}\n.help-panel .help-subheader {\n  margin: 0.8em 0 0.3em 1em;\n  font-size: 1.1em;\n  color: #ffcc00 !important;\n  font-weight: normal;\n  text-align: left;\n}\n.help-panel .help-section-header {\n  margin-top: 0 !important;\n  margin-bottom: 1.5em !important;\n  font-size: 1.5em !important;\n  text-align: center;\n  color: #ffa500 !important;\n}\n.help-panel .help-section-subheader {\n  margin: 0.8em 0 1.2em 1em;\n  font-size: 0.9em;\n  color: #00ff58 !important;\n  font-weight: normal;\n  text-align: left;\n}\n.help-panel .help-list {\n  list-style-type: none;\n  padding-left: 0;\n}\n.help-panel .help-list .help-list-item {\n  display: flex;\n  align-items: center;\n  flex-direction: row;\n  padding: 0.3em;\n  border-bottom: 1px dashed #444 !important;\n}\n.help-panel .help-list .help-list-item:last-child {\n  border-bottom: none;\n}\n.help-panel .help-list .help-list-item .help-hotkey {\n  width: fit-content;\n  display: flex;\n  color: #7ed4ff !important;\n  font-family: monospace;\n  background-color: rgba(126, 212, 255, 0.1019607843) !important;\n  border: 1px solid rgba(126, 212, 255, 0.4) !important;\n  border-left: 3px solid rgba(126, 212, 255, 0.4) !important;\n  border-radius: 0 0.2em 0.2em 0 !important;\n  padding: 2px 4px;\n  margin-right: 1em;\n}`, \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/styles/helpPanel.scss?./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js");

/***/ }),

/***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/style.scss":
/*!************************************************************************************************************!*\
  !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/style.scss ***!
  \************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports\n\n\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\n___CSS_LOADER_EXPORT___.push([module.id, \"@import url(https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap);\"]);\n___CSS_LOADER_EXPORT___.push([module.id, \"@import url(https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap);\"]);\n___CSS_LOADER_EXPORT___.push([module.id, \"@import url(https://fonts.googleapis.com/css2?family=Iansui&family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap);\"]);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, `#app-chat-container {\n  border-radius: 0.4em 0.4em 0 0 !important;\n  position: fixed;\n  bottom: 0;\n  left: 0;\n  height: 300px;\n  background: #1e1e1e !important;\n  border: 1px solid #333 !important;\n  display: flex;\n  font-family: sans-serif;\n  color: #cdb398 !important;\n  z-index: 999;\n  min-width: 320px !important;\n  min-height: 200px !important;\n  box-sizing: border-box;\n  max-width: 100vw;\n  overflow: hidden;\n  transition: opacity 0.3s ease, transform 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);\n}\n#app-chat-container a {\n  color: #82b32a !important;\n  transition: color 0.15s !important;\n}\n#app-chat-container a:hover {\n  color: #95cc30 !important;\n}\n#app-chat-container.maximized {\n  position: fixed;\n  z-index: 1010;\n}\n#app-chat-container:not(.visible-chat):not(.hidden-chat):not(.maximized):not(.floating-chat) {\n  display: none;\n  opacity: 0;\n}\n#app-chat-container.visible-chat {\n  transform: translateY(0) !important;\n}\n#app-chat-container.hidden-chat {\n  opacity: 1;\n  transform: translateY(calc(100% - 25px)) !important;\n}\n#app-chat-container.floating-chat {\n  border-radius: 0.4em !important;\n}\n#app-chat-container .font-size-control {\n  position: absolute !important;\n  top: 0 !important;\n  left: 0 !important;\n  display: flex !important;\n  align-items: center !important;\n  justify-content: center !important;\n  height: 25px !important;\n  padding: 0 10px !important;\n  gap: 5px !important;\n  z-index: 6 !important;\n}\n#app-chat-container .font-size-control .font-size-slider {\n  width: 80px !important;\n  height: 4px !important;\n  -webkit-appearance: none !important;\n  appearance: none !important;\n  background: #333 !important;\n  outline: none !important;\n  border-radius: 2px !important;\n  transition: opacity 0.2s !important;\n}\n#app-chat-container .font-size-control .font-size-slider::-webkit-slider-thumb {\n  -webkit-appearance: none !important;\n  appearance: none !important;\n  width: 10px !important;\n  height: 10px !important;\n  border-radius: 50% !important;\n  background: #cdb398 !important;\n  cursor: pointer !important;\n}\n#app-chat-container .font-size-control .font-size-slider::-moz-range-thumb {\n  width: 10px !important;\n  height: 10px !important;\n  border-radius: 50% !important;\n  background: #cdb398 !important;\n  cursor: pointer !important;\n  border: none !important;\n}\n#app-chat-container .resize-handle {\n  position: absolute !important;\n  background: transparent !important;\n  z-index: 1000 !important;\n}\n#app-chat-container .resize-handle.top {\n  top: -3px !important;\n  left: 0 !important;\n  right: 0 !important;\n  height: 6px !important;\n  cursor: ns-resize !important;\n}\n#app-chat-container .resize-handle.left {\n  left: -3px !important;\n  top: 0 !important;\n  bottom: 0 !important;\n  width: 6px !important;\n  cursor: ew-resize !important;\n}\n#app-chat-container .resize-handle.right {\n  right: -3px !important;\n  top: 0 !important;\n  bottom: 0 !important;\n  width: 6px !important;\n  cursor: ew-resize !important;\n}\n#app-chat-container .chat-wrapper {\n  display: flex !important;\n  flex-direction: row !important;\n  flex: 1 !important;\n  min-width: 320px !important;\n  overflow: hidden !important;\n}\n#app-chat-container .chat-content {\n  margin-top: 25px !important;\n  background: #1e1e1e !important;\n  display: flex !important;\n  flex-direction: column !important;\n  flex: 1 !important;\n  overflow: hidden !important;\n}\n#app-chat-container .messages-panel {\n  flex: 1 !important;\n  overflow-y: auto !important;\n  overflow-x: hidden !important;\n  padding: 1em !important;\n  display: flex !important;\n  flex-direction: column !important;\n  gap: 0.2em !important;\n  scrollbar-width: thin !important;\n  scrollbar-color: #333 #1e1e1e !important;\n}\n#app-chat-container .messages-panel.keyboard-active {\n  transition: margin-bottom 0.3s ease !important;\n}\n#app-chat-container .messages-panel::-webkit-scrollbar,\n#app-chat-container .messages-panel .user-list-container::-webkit-scrollbar {\n  width: 8px !important;\n}\n#app-chat-container .messages-panel::-webkit-scrollbar-thumb,\n#app-chat-container .messages-panel .user-list-container::-webkit-scrollbar-thumb {\n  background-color: #333 !important;\n}\n#app-chat-container .messages-panel::-webkit-scrollbar-thumb:hover,\n#app-chat-container .messages-panel .user-list-container::-webkit-scrollbar-thumb:hover {\n  background-color: #444 !important;\n}\n#app-chat-container .messages-panel::-webkit-scrollbar-track,\n#app-chat-container .messages-panel .user-list-container::-webkit-scrollbar-track {\n  background-color: #1e1e1e !important;\n}\n#app-chat-container .input-container {\n  display: flex !important;\n  align-items: center !important;\n  padding: 1em !important;\n  gap: 0.5em !important;\n  border-top: 1px solid #333 !important;\n  background-color: #1e1e1e !important;\n}\n#app-chat-container #message-input {\n  outline: none !important;\n  flex: 1 !important;\n  background: #2a2a2a !important;\n  color: #cdb398 !important;\n  padding: 0.5em !important;\n  border-radius: 0.2em !important;\n  min-width: 0 !important;\n  border: none !important;\n  position: relative;\n  font-family: inherit !important;\n  line-height: normal !important;\n  transition: font-size 0.2s ease !important;\n}\n#app-chat-container #message-input.private-mode {\n  background-color: rgba(255, 107, 107, 0.2196078431) !important;\n  color: #ff6b6b !important;\n  caret-color: #ff6b6b !important;\n}\n#app-chat-container #message-input.private-mode::placeholder {\n  color: rgba(255, 107, 107, 0.6);\n}\n#app-chat-container .length-field-popup {\n  position: absolute !important;\n  display: flex !important;\n  font-size: 12px !important;\n  font-weight: bold !important;\n  font-family: \"Montserrat\", Iansui, sans-serif !important;\n  bottom: 60px;\n  transition: left 100ms ease-out !important;\n  height: 20px !important;\n  align-items: center !important;\n  justify-content: center !important;\n  padding: 2px 4px;\n  opacity: 0;\n  border: none !important;\n  z-index: 101 !important;\n}\n#app-chat-container .user-list-container {\n  margin-top: 25px;\n  width: fit-content !important;\n  min-width: 180px !important;\n  max-width: fit-content !important;\n  overflow-y: auto !important;\n  overflow-x: hidden !important;\n  padding: 1em !important;\n  background: #1e1e1e !important;\n  border-left: 1px solid #333 !important;\n  scrollbar-width: thin !important;\n  scrollbar-color: #333 #1e1e1e !important;\n}\n#app-chat-container .reveal-userlist-btn {\n  position: absolute !important;\n  top: 50% !important;\n  right: -1px !important;\n  transform: translateY(-50%) !important;\n  z-index: 1000 !important;\n  display: flex !important;\n  align-items: center !important;\n  justify-content: center !important;\n  padding: 0.4em !important;\n  background: #222 !important;\n  font-size: 18px !important;\n  font-family: \"Noto Color Emoji\", sans-serif !important;\n  font-weight: bold !important;\n  border: 1px solid #333 !important;\n  border-radius: 0.4em 0 0 0.4em !important;\n  cursor: pointer !important;\n  transition: background 0.2s ease, opacity 0.2s ease !important;\n}\n#app-chat-container .reveal-userlist-btn:hover {\n  background: #333 !important;\n}\n#app-chat-container .hidden-userlist {\n  display: flex !important;\n}\n#app-chat-container .shown-userlist {\n  display: none !important;\n}\n#app-chat-container .message {\n  padding: 0.2em 0.4em !important;\n  display: flex;\n  flex-direction: row;\n  border-radius: 0.2em !important;\n  width: 100%;\n  max-width: 100% !important;\n  word-break: break-word !important;\n}\n#app-chat-container .message .message-text .emoji-adjuster {\n  font-family: \"Noto Color Emoji\", sans-serif !important;\n  font-size: 1.5em !important;\n  margin: 0 0.1em !important;\n  display: inline-flex !important;\n}\n#app-chat-container .message .message-text .mention {\n  display: inline-flex !important;\n  font-family: Montserrat !important;\n  font-weight: 500 !important;\n}\n#app-chat-container .message .message-text .md-heading {\n  font-family: \"Montserrat\", sans-serif !important;\n  margin: 0 !important;\n  padding: 0 !important;\n  color: #bf9d70 !important;\n}\n#app-chat-container .message .message-text .md-heading.md-h1 {\n  font-size: 1.8em !important;\n  font-weight: bold !important;\n}\n#app-chat-container .message .message-text .md-heading.md-h2 {\n  font-size: 1.6em !important;\n  font-weight: bold !important;\n}\n#app-chat-container .message .message-text .md-heading.md-h3 {\n  font-size: 1.4em !important;\n  font-weight: bold !important;\n}\n#app-chat-container .message .message-text .md-heading.md-h4 {\n  font-size: 1.2em !important;\n  font-weight: bold !important;\n}\n#app-chat-container .message .message-text .md-heading.md-h5 {\n  font-size: 1.1em !important;\n  font-weight: bold !important;\n}\n#app-chat-container .message .message-text .md-heading.md-h6 {\n  font-size: 1em !important;\n  font-weight: bold !important;\n}\n#app-chat-container .message .message-text code,\n#app-chat-container .message .message-text .md-code {\n  display: inline-flex !important;\n  font-size: 1em !important;\n  font-family: \"Consolas\", monospace !important;\n  background-color: #2a2a2a !important;\n  border: 1px solid #333 !important;\n  border-radius: 0.2em !important;\n  padding: 0.1em 0.2em !important;\n  color: #82b32a !important;\n  filter: none !important;\n  white-space: break-spaces !important;\n}\n#app-chat-container .message .message-text .md-bold {\n  font-weight: bold !important;\n  color: #cdb398 !important;\n}\n#app-chat-container .message .message-text .md-italic {\n  font-style: italic !important;\n  color: #cdb398 !important;\n}\n#app-chat-container .message .message-text .md-strikethrough {\n  text-decoration: line-through !important;\n  color: #cdb398 !important;\n}\n#app-chat-container .message.banned {\n  background-color: rgba(138, 43, 226, 0.1254901961) !important;\n  border: 1px solid rgba(138, 43, 226, 0.1882352941) !important;\n  border-left: 3px solid #8a2be2 !important;\n  width: fit-content !important;\n}\n#app-chat-container .message.banned .time {\n  color: #8a2be2 !important;\n}\n#app-chat-container .message.banned .username,\n#app-chat-container .message.banned .message-text {\n  color: #b875f7 !important;\n}\n#app-chat-container .message.system {\n  background-color: rgba(255, 165, 0, 0.1254901961) !important;\n  border: 1px solid rgba(255, 166, 0, 0.1882352941) !important;\n  border-left: 3px solid #ffa500 !important;\n  width: fit-content !important;\n  align-items: center !important;\n}\n#app-chat-container .message.system .time {\n  margin-right: unset !important;\n  color: rgba(255, 165, 0, 0.3764705882) !important;\n}\n#app-chat-container .message.system .username {\n  display: none !important;\n}\n#app-chat-container .message.system .message-text {\n  color: #ffa500 !important;\n}\n#app-chat-container .message.private-message {\n  width: fit-content !important;\n}\n#app-chat-container .message.private-message.sent {\n  background: rgba(0, 255, 0, 0.1254901961) !important;\n  border: 1px solid rgba(0, 255, 0, 0.1882352941) !important;\n  border-left: 3px solid #00ff00 !important;\n}\n#app-chat-container .message.private-message.sent .time {\n  color: rgba(0, 255, 0, 0.3764705882) !important;\n}\n#app-chat-container .message.private-message.sent .username,\n#app-chat-container .message.private-message.sent .message-text {\n  color: #00ff00 !important;\n}\n#app-chat-container .message.private-message.received {\n  background-color: rgba(255, 77, 77, 0.1254901961) !important;\n  border: 1px solid rgba(255, 77, 77, 0.1882352941) !important;\n  border-left: 3px solid #ff4d4d !important;\n}\n#app-chat-container .message.private-message.received .time {\n  color: rgba(255, 77, 77, 0.3764705882) !important;\n}\n#app-chat-container .message.private-message.received .username,\n#app-chat-container .message.private-message.received .message-text {\n  color: #ff4d4d !important;\n}\n#app-chat-container .message-info {\n  margin-right: 1em !important;\n  white-space: nowrap !important;\n}\n#app-chat-container .message-info .time {\n  font-size: 0.9em !important;\n  margin-right: 1em !important;\n  color: #666 !important;\n}\n#app-chat-container .message-info .username {\n  font-size: 1em !important;\n}\n#app-chat-container .username,\n#app-chat-container .time {\n  cursor: pointer !important;\n  transition: opacity 0.2s ease !important;\n}\n#app-chat-container .username:hover,\n#app-chat-container .time:hover {\n  opacity: 0.7 !important;\n}\n#app-chat-container .user-item {\n  display: flex !important;\n  align-items: center !important;\n  padding: 0.2em !important;\n  margin-bottom: 0.2em !important;\n  border-radius: 0.2em !important;\n  max-width: 100% !important;\n  text-overflow: ellipsis !important;\n}\n#app-chat-container .user-item .user-avatar {\n  display: flex !important;\n  justify-content: center !important;\n  align-items: center !important;\n  width: 24px !important;\n  height: 24px !important;\n  font-size: 18px !important;\n  border-radius: 0.1em !important;\n  margin-right: 1em !important;\n  text-align: center !important;\n  line-height: 24px !important;\n  flex-shrink: 0 !important;\n}\n#app-chat-container .user-item .user-avatar.image-avatar {\n  cursor: pointer !important;\n  transform-origin: left !important;\n  transition: transform 0.15s ease-out !important;\n}\n#app-chat-container .user-item .user-avatar.image-avatar:hover {\n  transform: scale(2) !important;\n}\n#app-chat-container .user-item .user-avatar.svg-avatar {\n  font-family: \"Noto Color Emoji\", sans-serif !important;\n}\n#app-chat-container .user-item .user-info {\n  flex: 1 !important;\n  min-width: 0 !important;\n  overflow: hidden !important;\n  text-overflow: ellipsis !important;\n  white-space: nowrap !important;\n}\n#app-chat-container .user-item .role,\n#app-chat-container .user-item .traffic-icon {\n  font-family: \"Noto Color Emoji\", sans-serif !important;\n}\n#app-chat-container .header-button {\n  position: absolute !important;\n  top: 0 !important;\n  width: 25px !important;\n  height: 25px !important;\n  z-index: 5 !important;\n}\n#app-chat-container .button {\n  display: flex !important;\n  align-items: center !important;\n  justify-content: center !important;\n  cursor: pointer !important;\n  background-color: transparent !important;\n  border: none !important;\n  outline: none !important;\n  margin: 0 !important;\n  padding: 0 !important;\n  transition: all 0.15s ease-out;\n}\n#app-chat-container .button:hover {\n  filter: brightness(1.2) !important;\n}\n#app-chat-container .emoji-trigger,\n#app-chat-container .private-mode-exit,\n#app-chat-container .send-button {\n  font-family: \"Noto Color Emoji\", sans-serif !important;\n  height: 28px !important;\n  width: 28px !important;\n  font-size: 1.5em !important;\n}\n#app-chat-container .chat-toggle-button {\n  right: 0 !important;\n}\n#app-chat-container .chat-maximize-button {\n  right: 25px !important;\n}\n#app-chat-container .chat-help-button {\n  color: #82b32a !important;\n  right: 50px !important;\n}\n#app-chat-container .chat-drag-area {\n  border-radius: 0.4em 0.4em 0 0 !important;\n  position: absolute !important;\n  top: 0 !important;\n  left: 0 !important;\n  right: 0 !important;\n  height: 25px !important;\n  cursor: move !important;\n  background-color: rgba(22, 22, 22, 0.8) !important;\n}\n#app-chat-container .clickable-thumbnail {\n  display: flex !important;\n  align-items: center;\n  opacity: 1;\n  transition: opacity 0.15s ease-in-out;\n  border: none !important;\n  max-width: 150px !important;\n  max-height: 150px !important;\n  cursor: pointer;\n  background-color: transparent;\n  margin: 6px;\n  overflow: hidden !important;\n  border-radius: 0.2em !important;\n  box-shadow: 0 0 2px rgba(0, 0, 0, 0.5) !important;\n}\n#app-chat-container .clickable-thumbnail:hover {\n  opacity: 0.8;\n}\n#app-chat-container .clickable-thumbnail img {\n  max-height: 100%;\n  max-width: 100%;\n  background-color: transparent;\n  object-fit: contain;\n}\n#app-chat-container .video-wrapper {\n  display: flex;\n  flex-direction: column;\n}\n#app-chat-container .video-wrapper .processed-video {\n  margin-bottom: 0.2em !important;\n}\n#app-chat-container .video-wrapper .youtube-info {\n  display: flex !important;\n  flex-direction: column !important;\n  margin-bottom: 0.2em !important;\n  font-family: \"Montserrat\", sans-serif !important;\n  font-size: 0.9em !important;\n  color: #8ede87 !important;\n  font-weight: 500 !important;\n  white-space: break-spaces !important;\n}\n#app-chat-container .video-container,\n#app-chat-container .youtube-thumb {\n  border-radius: 0.4em !important;\n  display: flex;\n  border: none;\n  height: 200px !important;\n  width: 356px !important;\n}\n#app-chat-container .youtube-thumb {\n  cursor: pointer !important;\n  object-fit: cover !important;\n}\n#app-chat-container .youtube-thumb:hover {\n  filter: brightness(0.8);\n  transition: filter 0.3s ease;\n}\n\n.dimming-element {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  background-color: rgba(0, 0, 0, 0.5019607843);\n  z-index: 1010 !important;\n  opacity: 0;\n  transition: opacity 0.3s ease;\n}\n\n.scaled-thumbnail {\n  top: 50%;\n  left: 50%;\n  transform-origin: center center;\n  transform: translate(-50%, -50%) scale(1);\n  position: fixed;\n  opacity: 0;\n  z-index: 1015 !important;\n  max-height: 90vh;\n  max-width: 90vw;\n  cursor: pointer;\n  border-radius: 0.6em !important;\n  box-shadow: 0 0 2px rgba(0, 0, 0, 0.5) !important;\n}\n\n.delete-btn {\n  z-index: 1020 !important;\n  padding: 8px 16px;\n  background-color: hsl(0, 50%, 20%);\n  color: hsl(0, 60%, 70%);\n  border: 1px solid hsl(0, 50%, 35%);\n  transition: all 0.3s;\n  cursor: pointer;\n  filter: brightness(1);\n  border-radius: 0.2em !important;\n}\n.delete-btn:hover {\n  filter: brightness(1.5);\n}\n\n.toggle-button {\n  font: bold 0.9em \"Montserrat\", sans-serif;\n  position: absolute;\n  top: 0;\n  right: 2em;\n  padding: 8px 16px;\n  transition: filter 0.3s;\n  border-radius: 0 0 0.2em 0.2em !important;\n  border-top: none;\n  min-width: 4em;\n}\n.toggle-button.toggle-hidden {\n  background: linear-gradient(to top, hsl(0, 50%, 20%), hsl(0, 50%, 25%));\n  color: hsl(0, 60%, 70%);\n  border-left: 1px solid hsl(0, 50%, 35%);\n  border-right: 1px solid hsl(0, 50%, 35%);\n  border-bottom: 1px solid hsl(0, 50%, 35%);\n}\n.toggle-button.toggle-shown {\n  background: linear-gradient(to top, hsl(30, 50%, 20%), hsl(30, 50%, 25%));\n  color: hsl(30, 60%, 70%);\n  border-left: 1px solid hsl(30, 50%, 35%);\n  border-right: 1px solid hsl(30, 50%, 35%);\n  border-bottom: 1px solid hsl(30, 50%, 35%);\n}\n.toggle-button:hover {\n  filter: brightness(1.5);\n}\n.toggle-button-hidden {\n  background-color: hsl(0, 20%, 10%);\n  color: hsl(0, 50%, 50%);\n  border: 1px solid hsl(0, 50%, 50%);\n}\n.toggle-button-show {\n  background-color: hsl(90, 20%, 10%);\n  color: hsl(90, 50%, 50%);\n  border: 1px solid hsl(90, 50%, 50%);\n}\n.toggle-button-hide {\n  background-color: hsl(50, 20%, 10%);\n  color: hsl(50, 50%, 50%);\n  border: 1px solid hsl(50, 50%, 50%);\n}\n\n.selected-message {\n  background-clip: padding-box !important;\n}\n.selected-message.message-mode {\n  background-color: hsla(0, 50%, 50%, 0.2) !important;\n  box-shadow: inset 0px 0px 0px 1px hsla(0, 50%, 50%, 0.4) !important;\n}\n.selected-message.username-mode {\n  background-color: hsla(145, 50%, 30%, 0.2) !important;\n  box-shadow: inset 0px 0px 0px 1px hsla(145, 50%, 50%, 0.4) !important;\n}\n.selected-message.time-mode {\n  background-color: hsla(200, 50%, 30%, 0.2) !important;\n  box-shadow: inset 0px 0px 0px 1px hsla(200, 50%, 50%, 0.4) !important;\n}\n\n.shown-message {\n  background-color: hsla(30, 60%, 30%, 0.2) !important;\n  box-shadow: inset 0px 0px 0px 1px hsla(30, 60%, 50%, 0.4) !important;\n  background-clip: padding-box !important;\n}\n\n.hidden-message {\n  display: none !important;\n}\n\n.new-messages-separator {\n  display: flex;\n  align-items: center;\n  height: 1em !important;\n}\n.new-messages-separator .separator-line {\n  flex-grow: 1 !important;\n  border: none !important;\n  border-top: 1px solid rgba(255, 132, 0, 0.568627451) !important;\n  margin: 0 !important;\n}\n.new-messages-separator .separator-icon {\n  background-color: rgba(255, 132, 0, 0.2509803922) !important;\n  border-radius: 0.2em !important;\n  padding: 0.2em 0.4em !important;\n  font-family: \"Noto Color Emoji\" !important;\n}\n\n.bounce-in {\n  animation: bounceIn 500ms forwards;\n}\n\n.bounce-out {\n  animation: bounceOut 500ms forwards;\n}\n\n@keyframes bounceIn {\n  0% {\n    transform: translateY(0);\n    opacity: 0;\n  }\n  50% {\n    transform: translateY(-10px);\n    opacity: 1;\n  }\n  100% {\n    transform: translateY(0);\n    opacity: 1;\n  }\n}\n@keyframes bounceOut {\n  0% {\n    transform: translateY(0);\n    opacity: 1;\n  }\n  50% {\n    transform: translateY(-10px);\n    opacity: 1;\n  }\n  100% {\n    transform: translateY(0);\n    opacity: 0;\n  }\n}\n@media (max-width: 780px) {\n  #app-chat-container .chat-wrapper {\n    width: 100% !important;\n    border-right: none !important;\n  }\n}\n@media screen and (max-width: 768px), (hover: none), (pointer: coarse) {\n  body {\n    background-color: #1e1e1e !important;\n  }\n  #app-chat-container {\n    height: 100% !important;\n    width: 100vw !important;\n    min-height: 100% !important;\n    min-width: 100vw !important;\n    border: none !important;\n    border-radius: 0 !important;\n    overflow: hidden !important;\n  }\n  #app-chat-container .resize-handle,\n  #app-chat-container .font-size-control,\n  #app-chat-container .header-button,\n  #app-chat-container #send-button {\n    display: none !important;\n  }\n  body > *:not(#app-chat-container):not(.dimming-element):not(.scaled-thumbnail):not(.delete-btn):not(.toggle-button):not(.update-overlay):not(.update-popup):not(.chat-username-color-picker) {\n    display: none !important;\n  }\n}\n@media screen and (max-width: 367px) {\n  #app-chat-container .video-container,\n  #app-chat-container .youtube-thumb {\n    transform-origin: left !important;\n    transform: scale(0.9) !important;\n  }\n}\n@media screen and (max-width: 350px) {\n  #app-chat-container .video-container,\n  #app-chat-container .youtube-thumb {\n    transform-origin: left !important;\n    transform: scale(0.8) !important;\n  }\n}`, \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/styles/style.scss?./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js");

/***/ }),

/***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/updateCheck.scss":
/*!******************************************************************************************************************!*\
  !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/updateCheck.scss ***!
  \******************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports\n\n\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, `.update-overlay {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  background-color: rgba(0, 0, 0, 0.5);\n  z-index: 1200;\n}\n\n.update-popup {\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  background: #1e1e1e;\n  padding: 1em;\n  border: 1px solid #333 !important;\n  border-radius: 0.4em !important;\n  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;\n  color: #cdb398 !important;\n  font-family: \"Gill Sans\", \"Gill Sans MT\", Calibri, \"Trebuchet MS\", sans-serif;\n  z-index: 1250;\n  max-width: 400px;\n  min-width: 300px;\n}\n.update-popup .update-header {\n  margin-top: 0 !important;\n  font-size: 1.5em !important;\n  text-align: center !important;\n  color: #ffa500 !important;\n}\n.update-popup .update-script {\n  margin: 1rem 0 !important;\n  text-align: center !important;\n  font-size: 1.2em !important;\n  color: #4285f4 !important;\n}\n.update-popup p {\n  margin: 0.5rem 0;\n  text-align: center;\n}\n.update-popup p .version {\n  font-weight: bold;\n  color: #ffa500 !important;\n}\n.update-popup .button-container {\n  display: flex;\n  justify-content: center;\n  margin-top: 15px;\n}\n.update-popup .button-container button {\n  padding: 5px 10px !important;\n  width: 100% !important;\n  border-radius: 0.2em !important;\n  cursor: pointer !important;\n  font-size: 1rem !important;\n  border: none !important;\n  transition: background-color 0.15s !important;\n}\n.update-popup .button-container button.update-later {\n  margin-right: 10px;\n  background: #f1f1f1;\n  border: 1px solid #ccc !important;\n}\n.update-popup .button-container button.update-skip {\n  margin-right: 10px;\n  background: #ffcc00;\n  color: #333;\n}\n.update-popup .button-container button.update-now {\n  background: #4285f4;\n  color: white;\n}\n.update-popup .button-container button:hover {\n  opacity: 0.9;\n}`, \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/styles/updateCheck.scss?./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js");

/***/ }),

/***/ "./node_modules/css-loader/dist/runtime/api.js":
/*!*****************************************************!*\
  !*** ./node_modules/css-loader/dist/runtime/api.js ***!
  \*****************************************************/
/***/ ((module) => {

eval("\r\n\r\n/*\r\n  MIT License http://www.opensource.org/licenses/mit-license.php\r\n  Author Tobias Koppers @sokra\r\n*/\r\nmodule.exports = function (cssWithMappingToString) {\r\n  var list = [];\r\n\r\n  // return the list of modules as css string\r\n  list.toString = function toString() {\r\n    return this.map(function (item) {\r\n      var content = \"\";\r\n      var needLayer = typeof item[5] !== \"undefined\";\r\n      if (item[4]) {\r\n        content += \"@supports (\".concat(item[4], \") {\");\r\n      }\r\n      if (item[2]) {\r\n        content += \"@media \".concat(item[2], \" {\");\r\n      }\r\n      if (needLayer) {\r\n        content += \"@layer\".concat(item[5].length > 0 ? \" \".concat(item[5]) : \"\", \" {\");\r\n      }\r\n      content += cssWithMappingToString(item);\r\n      if (needLayer) {\r\n        content += \"}\";\r\n      }\r\n      if (item[2]) {\r\n        content += \"}\";\r\n      }\r\n      if (item[4]) {\r\n        content += \"}\";\r\n      }\r\n      return content;\r\n    }).join(\"\");\r\n  };\r\n\r\n  // import a list of modules into the list\r\n  list.i = function i(modules, media, dedupe, supports, layer) {\r\n    if (typeof modules === \"string\") {\r\n      modules = [[null, modules, undefined]];\r\n    }\r\n    var alreadyImportedModules = {};\r\n    if (dedupe) {\r\n      for (var k = 0; k < this.length; k++) {\r\n        var id = this[k][0];\r\n        if (id != null) {\r\n          alreadyImportedModules[id] = true;\r\n        }\r\n      }\r\n    }\r\n    for (var _k = 0; _k < modules.length; _k++) {\r\n      var item = [].concat(modules[_k]);\r\n      if (dedupe && alreadyImportedModules[item[0]]) {\r\n        continue;\r\n      }\r\n      if (typeof layer !== \"undefined\") {\r\n        if (typeof item[5] === \"undefined\") {\r\n          item[5] = layer;\r\n        } else {\r\n          item[1] = \"@layer\".concat(item[5].length > 0 ? \" \".concat(item[5]) : \"\", \" {\").concat(item[1], \"}\");\r\n          item[5] = layer;\r\n        }\r\n      }\r\n      if (media) {\r\n        if (!item[2]) {\r\n          item[2] = media;\r\n        } else {\r\n          item[1] = \"@media \".concat(item[2], \" {\").concat(item[1], \"}\");\r\n          item[2] = media;\r\n        }\r\n      }\r\n      if (supports) {\r\n        if (!item[4]) {\r\n          item[4] = \"\".concat(supports);\r\n        } else {\r\n          item[1] = \"@supports (\".concat(item[4], \") {\").concat(item[1], \"}\");\r\n          item[4] = supports;\r\n        }\r\n      }\r\n      list.push(item);\r\n    }\r\n  };\r\n  return list;\r\n};\n\n//# sourceURL=webpack://tampermonkey-script/./node_modules/css-loader/dist/runtime/api.js?");

/***/ }),

/***/ "./node_modules/css-loader/dist/runtime/noSourceMaps.js":
/*!**************************************************************!*\
  !*** ./node_modules/css-loader/dist/runtime/noSourceMaps.js ***!
  \**************************************************************/
/***/ ((module) => {

eval("\r\n\r\nmodule.exports = function (i) {\r\n  return i[1];\r\n};\n\n//# sourceURL=webpack://tampermonkey-script/./node_modules/css-loader/dist/runtime/noSourceMaps.js?");

/***/ }),

/***/ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js":
/*!****************************************************************************!*\
  !*** ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js ***!
  \****************************************************************************/
/***/ ((module) => {

eval("\r\n\r\nvar stylesInDOM = [];\r\nfunction getIndexByIdentifier(identifier) {\r\n  var result = -1;\r\n  for (var i = 0; i < stylesInDOM.length; i++) {\r\n    if (stylesInDOM[i].identifier === identifier) {\r\n      result = i;\r\n      break;\r\n    }\r\n  }\r\n  return result;\r\n}\r\nfunction modulesToDom(list, options) {\r\n  var idCountMap = {};\r\n  var identifiers = [];\r\n  for (var i = 0; i < list.length; i++) {\r\n    var item = list[i];\r\n    var id = options.base ? item[0] + options.base : item[0];\r\n    var count = idCountMap[id] || 0;\r\n    var identifier = \"\".concat(id, \" \").concat(count);\r\n    idCountMap[id] = count + 1;\r\n    var indexByIdentifier = getIndexByIdentifier(identifier);\r\n    var obj = {\r\n      css: item[1],\r\n      media: item[2],\r\n      sourceMap: item[3],\r\n      supports: item[4],\r\n      layer: item[5]\r\n    };\r\n    if (indexByIdentifier !== -1) {\r\n      stylesInDOM[indexByIdentifier].references++;\r\n      stylesInDOM[indexByIdentifier].updater(obj);\r\n    } else {\r\n      var updater = addElementStyle(obj, options);\r\n      options.byIndex = i;\r\n      stylesInDOM.splice(i, 0, {\r\n        identifier: identifier,\r\n        updater: updater,\r\n        references: 1\r\n      });\r\n    }\r\n    identifiers.push(identifier);\r\n  }\r\n  return identifiers;\r\n}\r\nfunction addElementStyle(obj, options) {\r\n  var api = options.domAPI(options);\r\n  api.update(obj);\r\n  var updater = function updater(newObj) {\r\n    if (newObj) {\r\n      if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap && newObj.supports === obj.supports && newObj.layer === obj.layer) {\r\n        return;\r\n      }\r\n      api.update(obj = newObj);\r\n    } else {\r\n      api.remove();\r\n    }\r\n  };\r\n  return updater;\r\n}\r\nmodule.exports = function (list, options) {\r\n  options = options || {};\r\n  list = list || [];\r\n  var lastIdentifiers = modulesToDom(list, options);\r\n  return function update(newList) {\r\n    newList = newList || [];\r\n    for (var i = 0; i < lastIdentifiers.length; i++) {\r\n      var identifier = lastIdentifiers[i];\r\n      var index = getIndexByIdentifier(identifier);\r\n      stylesInDOM[index].references--;\r\n    }\r\n    var newLastIdentifiers = modulesToDom(newList, options);\r\n    for (var _i = 0; _i < lastIdentifiers.length; _i++) {\r\n      var _identifier = lastIdentifiers[_i];\r\n      var _index = getIndexByIdentifier(_identifier);\r\n      if (stylesInDOM[_index].references === 0) {\r\n        stylesInDOM[_index].updater();\r\n        stylesInDOM.splice(_index, 1);\r\n      }\r\n    }\r\n    lastIdentifiers = newLastIdentifiers;\r\n  };\r\n};\n\n//# sourceURL=webpack://tampermonkey-script/./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js?");

/***/ }),

/***/ "./node_modules/style-loader/dist/runtime/insertBySelector.js":
/*!********************************************************************!*\
  !*** ./node_modules/style-loader/dist/runtime/insertBySelector.js ***!
  \********************************************************************/
/***/ ((module) => {

eval("\r\n\r\nvar memo = {};\r\n\r\n/* istanbul ignore next  */\r\nfunction getTarget(target) {\r\n  if (typeof memo[target] === \"undefined\") {\r\n    var styleTarget = document.querySelector(target);\r\n\r\n    // Special case to return head of iframe instead of iframe itself\r\n    if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {\r\n      try {\r\n        // This will throw an exception if access to iframe is blocked\r\n        // due to cross-origin restrictions\r\n        styleTarget = styleTarget.contentDocument.head;\r\n      } catch (e) {\r\n        // istanbul ignore next\r\n        styleTarget = null;\r\n      }\r\n    }\r\n    memo[target] = styleTarget;\r\n  }\r\n  return memo[target];\r\n}\r\n\r\n/* istanbul ignore next  */\r\nfunction insertBySelector(insert, style) {\r\n  var target = getTarget(insert);\r\n  if (!target) {\r\n    throw new Error(\"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.\");\r\n  }\r\n  target.appendChild(style);\r\n}\r\nmodule.exports = insertBySelector;\n\n//# sourceURL=webpack://tampermonkey-script/./node_modules/style-loader/dist/runtime/insertBySelector.js?");

/***/ }),

/***/ "./node_modules/style-loader/dist/runtime/insertStyleElement.js":
/*!**********************************************************************!*\
  !*** ./node_modules/style-loader/dist/runtime/insertStyleElement.js ***!
  \**********************************************************************/
/***/ ((module) => {

eval("\r\n\r\n/* istanbul ignore next  */\r\nfunction insertStyleElement(options) {\r\n  var element = document.createElement(\"style\");\r\n  options.setAttributes(element, options.attributes);\r\n  options.insert(element, options.options);\r\n  return element;\r\n}\r\nmodule.exports = insertStyleElement;\n\n//# sourceURL=webpack://tampermonkey-script/./node_modules/style-loader/dist/runtime/insertStyleElement.js?");

/***/ }),

/***/ "./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js":
/*!**********************************************************************************!*\
  !*** ./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js ***!
  \**********************************************************************************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {

eval("\r\n\r\n/* istanbul ignore next  */\r\nfunction setAttributesWithoutAttributes(styleElement) {\r\n  var nonce =  true ? __webpack_require__.nc : 0;\r\n  if (nonce) {\r\n    styleElement.setAttribute(\"nonce\", nonce);\r\n  }\r\n}\r\nmodule.exports = setAttributesWithoutAttributes;\n\n//# sourceURL=webpack://tampermonkey-script/./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js?");

/***/ }),

/***/ "./node_modules/style-loader/dist/runtime/styleDomAPI.js":
/*!***************************************************************!*\
  !*** ./node_modules/style-loader/dist/runtime/styleDomAPI.js ***!
  \***************************************************************/
/***/ ((module) => {

eval("\r\n\r\n/* istanbul ignore next  */\r\nfunction apply(styleElement, options, obj) {\r\n  var css = \"\";\r\n  if (obj.supports) {\r\n    css += \"@supports (\".concat(obj.supports, \") {\");\r\n  }\r\n  if (obj.media) {\r\n    css += \"@media \".concat(obj.media, \" {\");\r\n  }\r\n  var needLayer = typeof obj.layer !== \"undefined\";\r\n  if (needLayer) {\r\n    css += \"@layer\".concat(obj.layer.length > 0 ? \" \".concat(obj.layer) : \"\", \" {\");\r\n  }\r\n  css += obj.css;\r\n  if (needLayer) {\r\n    css += \"}\";\r\n  }\r\n  if (obj.media) {\r\n    css += \"}\";\r\n  }\r\n  if (obj.supports) {\r\n    css += \"}\";\r\n  }\r\n  var sourceMap = obj.sourceMap;\r\n  if (sourceMap && typeof btoa !== \"undefined\") {\r\n    css += \"\\n/*# sourceMappingURL=data:application/json;base64,\".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), \" */\");\r\n  }\r\n\r\n  // For old IE\r\n  /* istanbul ignore if  */\r\n  options.styleTagTransform(css, styleElement, options.options);\r\n}\r\nfunction removeStyleElement(styleElement) {\r\n  // istanbul ignore if\r\n  if (styleElement.parentNode === null) {\r\n    return false;\r\n  }\r\n  styleElement.parentNode.removeChild(styleElement);\r\n}\r\n\r\n/* istanbul ignore next  */\r\nfunction domAPI(options) {\r\n  if (typeof document === \"undefined\") {\r\n    return {\r\n      update: function update() {},\r\n      remove: function remove() {}\r\n    };\r\n  }\r\n  var styleElement = options.insertStyleElement(options);\r\n  return {\r\n    update: function update(obj) {\r\n      apply(styleElement, options, obj);\r\n    },\r\n    remove: function remove() {\r\n      removeStyleElement(styleElement);\r\n    }\r\n  };\r\n}\r\nmodule.exports = domAPI;\n\n//# sourceURL=webpack://tampermonkey-script/./node_modules/style-loader/dist/runtime/styleDomAPI.js?");

/***/ }),

/***/ "./node_modules/style-loader/dist/runtime/styleTagTransform.js":
/*!*********************************************************************!*\
  !*** ./node_modules/style-loader/dist/runtime/styleTagTransform.js ***!
  \*********************************************************************/
/***/ ((module) => {

eval("\r\n\r\n/* istanbul ignore next  */\r\nfunction styleTagTransform(css, styleElement) {\r\n  if (styleElement.styleSheet) {\r\n    styleElement.styleSheet.cssText = css;\r\n  } else {\r\n    while (styleElement.firstChild) {\r\n      styleElement.removeChild(styleElement.firstChild);\r\n    }\r\n    styleElement.appendChild(document.createTextNode(css));\r\n  }\r\n}\r\nmodule.exports = styleTagTransform;\n\n//# sourceURL=webpack://tampermonkey-script/./node_modules/style-loader/dist/runtime/styleTagTransform.js?");

/***/ }),

/***/ "./src/auth.js":
/*!*********************!*\
  !*** ./src/auth.js ***!
  \*********************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   getAuthData: () => (/* binding */ getAuthData),\n/* harmony export */   klavoauth: () => (/* binding */ klavoauth),\n/* harmony export */   removeChatParams: () => (/* binding */ removeChatParams)\n/* harmony export */ });\nfunction removeChatParams() {\r\n  localStorage.removeItem('klavoauth');\r\n  localStorage.removeItem('chatUsernameColor');\r\n  setTimeout(() => {\r\n    window.location.href = 'https://klavogonki.ru/gamelist/';\r\n  }, 500);\r\n}\r\n\r\nconst klavoauth = {\r\n  get username() {\r\n    const data = localStorage.getItem('klavoauth');\r\n    return data ? JSON.parse(data).username : '';\r\n  },\r\n  get password() {\r\n    const data = localStorage.getItem('klavoauth');\r\n    return data ? JSON.parse(data).password : '';\r\n  }\r\n};\r\n\r\nfunction getAuthData() {\r\n  // Only proceed if on the gamelist page\r\n  if (!window.location.href.startsWith('https://klavogonki.ru/gamelist/')) return;\r\n\r\n  try {\r\n    // Find the script containing PageData\r\n    const script = Array.from(document.scripts).find(s => s.text.includes('PageData'));\r\n    if (!script) throw new Error('PageData script not found');\r\n\r\n    // Extract and parse the JSON-like data inside the script\r\n    const rawData = script.text.match(/\\.constant\\('PageData', ([\\s\\S]*?})\\)/)[1];\r\n    const parsedData = JSON.parse(\r\n      rawData\r\n        .replace(/(\\w+):/g, '\"$1\":') // Fix object keys\r\n        .replace(/'/g, '\"') // Fix string quotes\r\n    );\r\n\r\n    const username = `${parsedData.chatParams.user.id}#${parsedData.chatParams.user.login}`;\r\n    const password = parsedData.chatParams.pass;\r\n\r\n    // Redirect only if it hasn’t happened before\r\n    if (!localStorage.getItem('klavoauth')) {\r\n      // Always update klavoauth with the latest data\r\n      localStorage.setItem('klavoauth', JSON.stringify({ username, password }));\r\n      // Save separate key for chat color background value\r\n      localStorage.setItem('chatUsernameColor', parsedData.chatParams.user.background);\r\n      setTimeout(() => {\r\n        window.location.href = 'https://klavogonki.ru';\r\n      }, 500);\r\n    }\r\n  } catch (e) {\r\n    console.error('Auth error:', e);\r\n    removeChatParams();\r\n\r\n    alert(`Auth failed: ${e.message}\\nPlease refresh the page.`);\r\n  }\r\n}\n\n//# sourceURL=webpack://tampermonkey-script/./src/auth.js?");

/***/ }),

/***/ "./src/chat/chatEvents.js":
/*!********************************!*\
  !*** ./src/chat/chatEvents.js ***!
  \********************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   setupDragHandlers: () => (/* binding */ setupDragHandlers),\n/* harmony export */   setupResizeHandlers: () => (/* binding */ setupResizeHandlers),\n/* harmony export */   setupWindowResizeHandler: () => (/* binding */ setupWindowResizeHandler)\n/* harmony export */ });\n/* harmony import */ var _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../helpers/helpers.js */ \"./src/helpers/helpers.js\");\n\r\n\r\n// ------------------------- Drag Handlers (Floating) -------------------------\r\nlet isDragging = false,\r\n    dragStartX,\r\n    dragStartY,\r\n    dragStartLeft,\r\n    dragStartTop;\r\n\r\nfunction setupDragHandlers() {\r\n  document.addEventListener('mousedown', (e) => {\r\n    const dragArea = e.target.closest('.chat-drag-area');\r\n    if (!dragArea) return;\r\n\r\n    const chat = document.getElementById('app-chat-container');\r\n    let chatState = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.getChatState)();\r\n\r\n    isDragging = true;\r\n    dragStartX = e.clientX;\r\n    dragStartY = e.clientY;\r\n    dragStartLeft = chat.offsetLeft;\r\n    // Use the computed top if no inline style exists\r\n    dragStartTop = parseInt(chat.style.top) || chat.getBoundingClientRect().top;\r\n    \r\n    // If the chat isn’t already floating, convert it now\r\n    if (!chatState.floating) {\r\n      const newTop = window.innerHeight - chat.offsetHeight;\r\n      chat.style.top = newTop + 'px';\r\n      chat.style.bottom = '';\r\n      chatState.top = newTop;\r\n      chatState.floating = true;\r\n      chat.classList.add(\"floating-chat\");\r\n      (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.saveChatState)(chatState);\r\n    }\r\n\r\n    document.body.style.userSelect = 'none';\r\n  });\r\n\r\n  document.addEventListener('mousemove', (e) => {\r\n    if (!isDragging) return;\r\n\r\n    const chat = document.getElementById('app-chat-container');\r\n    const viewportWidth = window.innerWidth;\r\n    const viewportHeight = window.innerHeight;\r\n    const deltaX = e.clientX - dragStartX;\r\n    const deltaY = e.clientY - dragStartY;\r\n    const newLeft = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.clamp)(dragStartLeft + deltaX, 0, viewportWidth - chat.offsetWidth);\r\n    // Ensure the chat does not go above the viewport (>= 0) or below viewport bottom\r\n    const newTop = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.clamp)(dragStartTop + deltaY, 0, viewportHeight - chat.offsetHeight);\r\n    \r\n    chat.style.left = newLeft + 'px';\r\n    chat.style.top = newTop + 'px';\r\n    \r\n    let chatState = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.getChatState)();\r\n    chatState.left = newLeft;\r\n    chatState.top = newTop;\r\n    chatState.floating = true;\r\n    (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.saveChatState)(chatState);\r\n  });\r\n\r\n  document.addEventListener('mouseup', () => {\r\n    if (!isDragging) return;\r\n    isDragging = false;\r\n\r\n    const chat = document.getElementById('app-chat-container');\r\n    const viewportWidth = window.innerWidth;\r\n    const viewportHeight = window.innerHeight;\r\n    const chatRect = chat.getBoundingClientRect();\r\n    const SNAP_THRESHOLD = 50;\r\n    \r\n    const outOfBounds = chatRect.left < 0 || chatRect.top < 0 ||\r\n                        chatRect.right > viewportWidth || chatRect.bottom > viewportHeight;\r\n    const nearBottom = (viewportHeight - chatRect.bottom) < SNAP_THRESHOLD;\r\n    \r\n    let chatState = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.getChatState)();\r\n    if (outOfBounds || nearBottom) {\r\n      // Snap to bottom when out of bounds\r\n      chat.style.top = '';\r\n      chat.style.bottom = '0';\r\n      chatState.floating = false;\r\n      chat.classList.remove(\"floating-chat\");\r\n    } else {\r\n      chatState.floating = true;\r\n      chatState.top = chatRect.top;\r\n      chat.classList.add(\"floating-chat\");\r\n    }\r\n    \r\n    (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.saveChatState)(chatState);\r\n    document.body.style.userSelect = '';\r\n  });\r\n}\r\n\r\n// ------------------------- Resize Handlers -------------------------\r\nlet isResizing = false,\r\n    resizeType = null,\r\n    startX,\r\n    startY,\r\n    startWidth,\r\n    startHeight,\r\n    startLeft,\r\n    startTop,\r\n    offsetY = 0;  // used to keep the cursor's relative position on the edge\r\n\r\nfunction setupResizeHandlers() {\r\n  document.addEventListener('mousedown', (e) => {\r\n    const handle = e.target.closest('.resize-handle');\r\n    if (!handle) return;\r\n    isResizing = true;\r\n    resizeType = handle.classList[1]; // e.g., 'top', 'left', 'right'\r\n    const chat = document.getElementById('app-chat-container');\r\n    startX = e.clientX;\r\n    startY = e.clientY;\r\n    startWidth = chat.offsetWidth;\r\n    startHeight = chat.offsetHeight;\r\n    startLeft = chat.offsetLeft;\r\n    // Use inline style top if available, otherwise the computed top\r\n    startTop = parseInt(chat.style.top) || chat.getBoundingClientRect().top;\r\n    offsetY = e.clientY - startTop;  // capture the cursor offset relative to chat's top edge\r\n    document.body.style.userSelect = 'none';\r\n  });\r\n\r\n  document.addEventListener('mousemove', (e) => {\r\n    if (!isResizing) return;\r\n    const chat = document.getElementById('app-chat-container');\r\n    const viewportWidth = window.innerWidth;\r\n    const viewportHeight = window.innerHeight;\r\n    const computedStyle = getComputedStyle(document.documentElement);\r\n    const minWidth = parseInt(computedStyle.getPropertyValue('--min-chat-width')) || 250;\r\n    const minHeight = parseInt(computedStyle.getPropertyValue('--min-chat-height')) || 200;\r\n    let chatState = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.getChatState)();\r\n\r\n    // Horizontal resizing (applies for left and right handles)\r\n    if (resizeType === 'left') {\r\n      const newWidth = Math.max(minWidth, startWidth - (e.clientX - startX));\r\n      const newLeft = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.clamp)(startLeft + (e.clientX - startX), 0, viewportWidth - newWidth);\r\n      chat.style.width = newWidth + 'px';\r\n      chat.style.left = newLeft + 'px';\r\n      chatState.width = newWidth;\r\n      chatState.left = newLeft;\r\n    } else if (resizeType === 'right') {\r\n      const maxWidth = viewportWidth - chat.getBoundingClientRect().left;\r\n      const newWidth = Math.min(maxWidth, Math.max(minWidth, startWidth + (e.clientX - startX)));\r\n      chat.style.width = newWidth + 'px';\r\n      chatState.width = newWidth;\r\n    }\r\n\r\n    // Vertical resizing for the three handles will use the cursor’s Y position.\r\n    // Calculate the desired new top edge based on the original offset.\r\n    const newTopCandidate = e.clientY - offsetY;\r\n\r\n    // For the top handle, the chat’s top edge should follow the cursor.\r\n    if (resizeType === 'top') {\r\n      if (chatState.floating === false) {\r\n        // Docked: bottom remains anchored at viewport bottom.\r\n        // When docked, the computed startTop equals (viewportHeight - startHeight)\r\n        let newTop = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.clamp)(newTopCandidate, 0, viewportHeight - minHeight);\r\n        let newHeight = viewportHeight - newTop; // bottom is fixed at viewport bottom\r\n        chat.style.top = newTop + 'px';\r\n        chat.style.height = newHeight + 'px';\r\n        chatState.top = newTop;\r\n        chatState.height = newHeight;\r\n      } else {\r\n        // Floating: simply follow the cursor for the top edge,\r\n        // ensuring we don’t shrink below minHeight.\r\n        let newTop = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.clamp)(newTopCandidate, 0, startTop + startHeight - minHeight);\r\n        let newHeight = startHeight - (newTop - startTop);\r\n        // Also do not allow height to exceed viewport height\r\n        newHeight = Math.min(newHeight, viewportHeight);\r\n        chat.style.top = newTop + 'px';\r\n        chat.style.height = newHeight + 'px';\r\n        chatState.top = newTop;\r\n        chatState.height = newHeight;\r\n      }\r\n    }\r\n    \r\n    // For left/right handles, we also want vertical resizing.\r\n    if (resizeType === 'left' || resizeType === 'right') {\r\n      if (chatState.floating === false) {\r\n        // Docked: bottom is fixed\r\n        let newTop = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.clamp)(newTopCandidate, 0, viewportHeight - minHeight);\r\n        let newHeight = viewportHeight - newTop;\r\n        chat.style.top = newTop + 'px';\r\n        chat.style.height = newHeight + 'px';\r\n        chatState.top = newTop;\r\n        chatState.height = newHeight;\r\n      } else {\r\n        // Floating: follow the cursor while keeping at least minHeight.\r\n        let newTop = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.clamp)(newTopCandidate, 0, startTop + startHeight - minHeight);\r\n        let newHeight = startHeight - (newTop - startTop);\r\n        newHeight = Math.min(newHeight, viewportHeight - newTop);\r\n        chat.style.top = newTop + 'px';\r\n        chat.style.height = newHeight + 'px';\r\n        chatState.top = newTop;\r\n        chatState.height = newHeight;\r\n      }\r\n    }\r\n\r\n    // Ensure height never exceeds the viewport height\r\n    if (chat.offsetHeight > viewportHeight) {\r\n      chat.style.height = viewportHeight + 'px';\r\n      if (chatState.floating === false) {\r\n        chat.style.top = '0px';  // if docked, force top to 0 so height = viewport height\r\n        chatState.top = 0;\r\n      }\r\n      chatState.height = viewportHeight;\r\n    }\r\n    \r\n    (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.saveChatState)(chatState);\r\n    (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.handleElementsBehavior)();\r\n  });\r\n\r\n  document.addEventListener('mouseup', () => {\r\n    isResizing = false;\r\n    document.body.style.userSelect = '';\r\n  });\r\n}\r\n\r\nfunction setupWindowResizeHandler() {\r\n  window.addEventListener('resize', () => {\r\n    (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.restoreChatState)();\r\n    (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.handleElementsBehavior)();\r\n  });\r\n}\r\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/chat/chatEvents.js?");

/***/ }),

/***/ "./src/chat/chatMessagesRemover.js":
/*!*****************************************!*\
  !*** ./src/chat/chatMessagesRemover.js ***!
  \*****************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (/* binding */ ChatMessagesRemover),\n/* harmony export */   pruneDeletedMessages: () => (/* binding */ pruneDeletedMessages)\n/* harmony export */ });\n/* harmony import */ var _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../helpers/helpers.js */ \"./src/helpers/helpers.js\");\n\r\n\r\nclass ChatMessagesRemover {\r\n  constructor() {\r\n    this.selected = new Set();\r\n    this.isDragging = false;\r\n    this.toggleBtn = null;\r\n    this.longPressTimer = null;\r\n    this.longPressDuration = 500;\r\n    this.touchStartX = 0;\r\n    this.touchStartY = 0;\r\n    this.isMobile = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.checkIsMobile)();\r\n    this.init();\r\n  }\r\n\r\n  init() {\r\n    this.attachEvents();\r\n    this.updateDeletedMessages();\r\n    this.renderToggle();\r\n  }\r\n\r\n  attachEvents() {\r\n    document.addEventListener(\"mousedown\", (e) => {\r\n      const msgEl = e.target.closest(\".messages-panel .message\");\r\n      if (!msgEl) return;\r\n\r\n      if (e.button === 2 && msgEl) {\r\n        this.handleSelection(e.target, msgEl, e.ctrlKey);\r\n      }\r\n    });\r\n\r\n    document.addEventListener(\"mouseup\", () => (this.isDragging = false));\r\n\r\n    document.addEventListener(\"mousemove\", (e) => {\r\n      if (!this.isDragging) return;\r\n\r\n      const msgEl = e.target.closest(\".messages-panel .message\");\r\n      if (msgEl) {\r\n        this.toggleSelect(msgEl, true, \"message-mode\");\r\n      }\r\n    });\r\n\r\n    document.addEventListener(\"contextmenu\", (e) => {\r\n      const msg = e.target.closest(\".messages-panel .message\");\r\n      if (msg) {\r\n        e.preventDefault();\r\n        this.showDeleteButton(e, msg);\r\n      }\r\n    });\r\n\r\n    if (this.isMobile) {\r\n      document.addEventListener(\"touchstart\", (e) => {\r\n        const msgEl = e.target.closest(\".messages-panel .message\");\r\n        if (!msgEl) return;\r\n\r\n        this.touchStartX = e.touches[0].clientX;\r\n        this.touchStartY = e.touches[0].clientY;\r\n\r\n        this.longPressTimer = setTimeout(() => {\r\n          this.handleSelection(e.target, msgEl, false);\r\n          this.showDeleteButton({\r\n            clientX: this.touchStartX,\r\n            clientY: this.touchStartY,\r\n            preventDefault: () => { }\r\n          }, msgEl);\r\n          if (navigator.vibrate) {\r\n            navigator.vibrate(50);\r\n          }\r\n        }, this.longPressDuration);\r\n      });\r\n\r\n      document.addEventListener(\"touchmove\", (e) => {\r\n        if (this.isDragging) {\r\n          e.preventDefault(); // Prevent scrolling during multi-selection drag\r\n        }\r\n        const touchX = e.touches[0].clientX;\r\n        const touchY = e.touches[0].clientY;\r\n        const moveX = Math.abs(touchX - this.touchStartX);\r\n        const moveY = Math.abs(touchY - this.touchStartY);\r\n\r\n        if (moveX > 10 || moveY > 10) {\r\n          if (this.longPressTimer) {\r\n            clearTimeout(this.longPressTimer);\r\n            this.longPressTimer = null;\r\n          }\r\n        }\r\n      }, { passive: false });\r\n\r\n      document.addEventListener(\"touchend\", () => {\r\n        if (this.longPressTimer) {\r\n          clearTimeout(this.longPressTimer);\r\n          this.longPressTimer = null;\r\n        }\r\n      });\r\n    }\r\n  }\r\n\r\n  handleSelection(target, msgEl, isCtrlKey) {\r\n    const timeEl = target.closest(\".time\");\r\n    const usernameEl = target.closest(\".username\");\r\n\r\n    if (timeEl) {\r\n      this.handleTimeSelection(msgEl, isCtrlKey);\r\n    } else if (usernameEl) {\r\n      this.handleUsernameSelection(usernameEl);\r\n    } else {\r\n      this.isDragging = true;\r\n      this.toggleSelect(msgEl, true, \"message-mode\");\r\n    }\r\n  }\r\n\r\n  handleTimeSelection(msgEl, isCtrlKey) {\r\n    const messages = Array.from(\r\n      document.querySelectorAll(\".messages-panel .message\")\r\n    );\r\n    const startIndex = messages.indexOf(msgEl);\r\n\r\n    if (startIndex === -1) return;\r\n\r\n    if (isCtrlKey) {\r\n      messages.slice(startIndex).forEach((m) => {\r\n        this.toggleSelect(m, true, \"time-mode\");\r\n        m.classList.add(\"time-mode\");\r\n      });\r\n    } else {\r\n      const usernameEl = msgEl.querySelector(\".username\");\r\n      if (!usernameEl) return;\r\n\r\n      const usernameText = usernameEl.textContent.trim();\r\n      messages.slice(startIndex).forEach((m) => {\r\n        const mUsernameEl = m.querySelector(\".username\");\r\n        if (\r\n          mUsernameEl &&\r\n          mUsernameEl.textContent.trim() === usernameText\r\n        ) {\r\n          this.toggleSelect(m, true, \"time-mode\");\r\n          m.classList.add(\"time-mode\");\r\n        }\r\n      });\r\n    }\r\n  }\r\n\r\n  handleUsernameSelection(usernameEl) {\r\n    const usernameText = usernameEl.textContent.trim();\r\n    document.querySelectorAll(\".messages-panel .message\").forEach((msg) => {\r\n      const msgUsernameEl = msg.querySelector(\".username\");\r\n      if (\r\n        msgUsernameEl &&\r\n        msgUsernameEl.textContent.trim() === usernameText\r\n      ) {\r\n        this.toggleSelect(msg, true, \"username-mode\");\r\n        msg.classList.add(\"username-mode\");\r\n      }\r\n    });\r\n  }\r\n\r\n  toggleSelect(el, state, mode = \"message-mode\") {\r\n    if (!el) return;\r\n\r\n    el.classList.toggle(\"selected-message\", state);\r\n\r\n    if (!state) {\r\n      el.classList.remove(\"username-mode\", \"time-mode\", \"message-mode\");\r\n    } else if (mode === \"message-mode\") {\r\n      el.classList.add(\"message-mode\");\r\n    }\r\n\r\n    const id = getMessageId(el);\r\n    state ? this.selected.add(id) : this.selected.delete(id);\r\n  }\r\n\r\n  showDeleteButton(e, msg) {\r\n    const existingBtn = document.querySelector(\".delete-btn\");\r\n    if (existingBtn) existingBtn.remove();\r\n\r\n    const btn = document.createElement(\"button\");\r\n    btn.className = \"delete-btn\";\r\n\r\n    if (this.isMobile) {\r\n      btn.classList.add(\"mobile-delete-btn\");\r\n    }\r\n\r\n    btn.textContent = \"Delete\";\r\n\r\n    // Prevent touch events on the button from bubbling up to any global \"outside tap\" handlers.\r\n    btn.addEventListener(\"touchstart\", (e) => {\r\n      e.stopPropagation();\r\n    });\r\n\r\n    document.body.append(btn);\r\n    const { offsetWidth: w, offsetHeight: h } = btn;\r\n    btn.remove();\r\n\r\n    Object.assign(btn.style, {\r\n      position: \"fixed\",\r\n      top: `${e.clientY - h / 2}px`,\r\n      left: `${e.clientX - w / 2}px`,\r\n    });\r\n\r\n    btn.onclick = () => this.deleteSelectedMessages(btn);\r\n\r\n    if (this.isMobile) {\r\n      const handleOutsideTap = (event) => {\r\n        if (!btn.contains(event.target)) {\r\n          this.clearSelection();\r\n          btn.remove();\r\n          document.removeEventListener('touchstart', handleOutsideTap);\r\n        }\r\n      };\r\n      document.addEventListener('touchstart', handleOutsideTap);\r\n      btn.outsideTapHandler = handleOutsideTap;\r\n    }\r\n\r\n    let timeoutId;\r\n    btn.addEventListener(\"mouseenter\", () => {\r\n      if (timeoutId) clearTimeout(timeoutId);\r\n    });\r\n\r\n    btn.addEventListener(\"mouseleave\", () => {\r\n      timeoutId = setTimeout(() => {\r\n        btn.remove();\r\n        this.clearSelection();\r\n      }, 1000);\r\n    });\r\n\r\n    document.body.append(btn);\r\n  }\r\n\r\n  deleteSelectedMessages(btn) {\r\n    document.querySelectorAll(\".selected-message\").forEach((msg) => {\r\n      if (!msg) return;\r\n\r\n      msg.classList.remove(\"selected-message\", \"username-mode\", \"time-mode\", \"message-mode\");\r\n      if (msg.classList.length === 0) msg.removeAttribute(\"class\");\r\n    });\r\n    this.storeDeleted([...this.selected]);\r\n    btn.remove();\r\n    if (this.isMobile && btn.outsideTapHandler) {\r\n      document.removeEventListener('touchstart', btn.outsideTapHandler);\r\n    }\r\n    this.selected.clear();\r\n    this.updateDeletedMessages();\r\n    this.renderToggle();\r\n  }\r\n\r\n  clearSelection() {\r\n    document.querySelectorAll(\".selected-message\").forEach((msg) => {\r\n      if (!msg) return;\r\n\r\n      msg.classList.remove(\"selected-message\", \"username-mode\", \"time-mode\", \"message-mode\");\r\n      if (msg.classList.length === 0) {\r\n        msg.removeAttribute(\"class\");\r\n      }\r\n    });\r\n    this.selected.clear();\r\n  }\r\n\r\n  storeDeleted(ids) {\r\n    const stored = new Set(\r\n      JSON.parse(localStorage.getItem(\"deletedChatMessagesContent\") || \"[]\")\r\n    );\r\n    ids.forEach((id) => stored.add(id));\r\n    localStorage.setItem(\"deletedChatMessagesContent\", JSON.stringify([...stored]));\r\n  }\r\n\r\n  updateDeletedMessages() {\r\n    const stored = new Set(\r\n      JSON.parse(localStorage.getItem(\"deletedChatMessagesContent\") || \"[]\")\r\n    );\r\n\r\n    const messages = document.querySelectorAll(\".messages-panel .message\");\r\n    if (messages.length === 0) return;\r\n\r\n    messages.forEach((msg) => {\r\n      if (!msg) return;\r\n\r\n      const id = getMessageId(msg);\r\n      msg.classList.remove(\"shown-message\");\r\n      msg.classList.toggle(\"hidden-message\", stored.has(id));\r\n    });\r\n\r\n    localStorage.setItem(\"deletedChatMessagesContent\", JSON.stringify([...stored]));\r\n  }\r\n\r\n  renderToggle() {\r\n    const storedItems = JSON.parse(localStorage.getItem(\"deletedChatMessagesContent\") || \"[]\");\r\n    const hasDeleted = storedItems.length > 0;\r\n\r\n    if (!hasDeleted) {\r\n      if (this.toggleBtn) {\r\n        this.toggleBtn.remove();\r\n        this.toggleBtn = null;\r\n      }\r\n      return;\r\n    }\r\n\r\n    const messagesPanel = document.querySelector(\".messages-panel\");\r\n    if (!messagesPanel) return;\r\n\r\n    if (!this.toggleBtn) {\r\n      this.toggleBtn = document.createElement(\"button\");\r\n      this.toggleBtn.className = \"toggle-button toggle-hidden\";\r\n      this.toggleBtn.textContent = \"Show\";\r\n\r\n      this.toggleBtn.onclick = (e) => {\r\n        if (e.ctrlKey) {\r\n          this.restoreAllMessages();\r\n          return;\r\n        }\r\n\r\n        const shouldShow = this.toggleBtn.textContent === \"Show\";\r\n        const storedIds = JSON.parse(\r\n          localStorage.getItem(\"deletedChatMessagesContent\") || \"[]\"\r\n        );\r\n\r\n        document.querySelectorAll(\".messages-panel .message\").forEach((msg) => {\r\n          if (!msg) return;\r\n\r\n          const id = getMessageId(msg);\r\n          if (storedIds.includes(id)) {\r\n            msg.classList.toggle(\"hidden-message\", !shouldShow);\r\n            msg.classList.toggle(\"shown-message\", shouldShow);\r\n          }\r\n        });\r\n\r\n        if (shouldShow) {\r\n          this.toggleBtn.textContent = \"Hide\";\r\n          this.toggleBtn.classList.remove(\"toggle-hidden\");\r\n          this.toggleBtn.classList.add(\"toggle-shown\");\r\n        } else {\r\n          this.toggleBtn.textContent = \"Show\";\r\n          this.toggleBtn.classList.remove(\"toggle-shown\");\r\n          this.toggleBtn.classList.add(\"toggle-hidden\");\r\n        }\r\n      };\r\n\r\n      if (this.isMobile) {\r\n        let isLongPress = false;\r\n        let longPressTimer;\r\n\r\n        this.toggleBtn.addEventListener('touchstart', (e) => {\r\n          this.touchStartX = e.touches[0].clientX;\r\n          this.touchStartY = e.touches[0].clientY;\r\n          isLongPress = false;\r\n          longPressTimer = setTimeout(() => {\r\n            isLongPress = true;\r\n            this.restoreAllMessages();\r\n            if (navigator.vibrate) {\r\n              navigator.vibrate(50);\r\n            }\r\n          }, this.longPressDuration);\r\n        });\r\n\r\n        this.toggleBtn.addEventListener('touchmove', (e) => {\r\n          const touchX = e.touches[0].clientX;\r\n          const touchY = e.touches[0].clientY;\r\n          const moveX = Math.abs(touchX - this.touchStartX);\r\n          const moveY = Math.abs(touchY - this.touchStartY);\r\n          if (moveX > 10 || moveY > 10) {\r\n            clearTimeout(longPressTimer);\r\n          }\r\n        });\r\n\r\n        this.toggleBtn.addEventListener('touchend', () => {\r\n          clearTimeout(longPressTimer);\r\n          if (isLongPress) {\r\n            this.toggleBtn.addEventListener('click', (e) => {\r\n              e.preventDefault();\r\n              e.stopPropagation();\r\n            }, { once: true });\r\n          }\r\n        });\r\n      }\r\n\r\n      messagesPanel.append(this.toggleBtn);\r\n    }\r\n  }\r\n\r\n  restoreAllMessages() {\r\n    document.querySelectorAll(\".messages-panel .message\").forEach((msg) => {\r\n      if (!msg) return;\r\n\r\n      msg.classList.remove(\"hidden-message\", \"shown-message\");\r\n    });\r\n    localStorage.setItem(\"deletedChatMessagesContent\", JSON.stringify([]));\r\n    this.selected.clear();\r\n    this.updateDeletedMessages();\r\n    this.renderToggle();\r\n  }\r\n}\r\n\r\nfunction getMessageId(el) {\r\n  if (!el) return '';\r\n  if (el.dataset.messageId) return el.dataset.messageId;\r\n\r\n  let id = Array.from(el.childNodes)\r\n    .map((n) => {\r\n      if (!n) return '';\r\n      if (n.nodeType === Node.TEXT_NODE) return n.textContent.trim();\r\n      if (n.classList?.contains(\"username\")) return n.textContent.trim();\r\n      if (n.tagName === \"A\") return n.href;\r\n      if (n.tagName === \"IMG\") return n.title.trim();\r\n      if (n.tagName === \"IFRAME\") return n.src.trim();\r\n      return \"\";\r\n    })\r\n    .join(\"\");\r\n\r\n  if (!id) {\r\n    id = 'msg-' + Math.random().toString(36).substring(2, 7);\r\n  }\r\n  el.dataset.messageId = id;\r\n  return id;\r\n}\r\n\r\nfunction pruneDeletedMessages() {\r\n  const messages = document.querySelectorAll(\".messages-panel .message\");\r\n  if (messages.length === 0) return;\r\n\r\n  const currentIds = new Set(\r\n    Array.from(messages).map((msg) => getMessageId(msg))\r\n  );\r\n\r\n  const stored = new Set(\r\n    JSON.parse(localStorage.getItem(\"deletedChatMessagesContent\") || \"[]\")\r\n  );\r\n\r\n  localStorage.setItem(\r\n    \"deletedChatMessagesContent\",\r\n    JSON.stringify([...stored].filter((id) => currentIds.has(id)))\r\n  );\r\n}\n\n//# sourceURL=webpack://tampermonkey-script/./src/chat/chatMessagesRemover.js?");

/***/ }),

/***/ "./src/chat/chatUI.js":
/*!****************************!*\
  !*** ./src/chat/chatUI.js ***!
  \****************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   createChatUI: () => (/* binding */ createChatUI)\n/* harmony export */ });\n/* harmony import */ var _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../helpers/helpers.js */ \"./src/helpers/helpers.js\");\n/* harmony import */ var _data_icons_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../data/icons.js */ \"./src/data/icons.js\");\n/* harmony import */ var _components_helpPanel_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../components/helpPanel.js */ \"./src/components/helpPanel.js\");\n/* harmony import */ var _components_emojiPanel_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../components/emojiPanel.js */ \"./src/components/emojiPanel.js\");\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nfunction createChatUI() {\r\n  const chatContainer = document.createElement('div');\r\n  chatContainer.id = 'app-chat-container';\r\n\r\n  // Add resize handles\r\n  ['top', 'left', 'right'].forEach(type => {\r\n    const handle = document.createElement('div');\r\n    handle.className = `resize-handle ${type}`;\r\n    chatContainer.appendChild(handle);\r\n  });\r\n\r\n  // Chat wrapper for content and user list\r\n  const chatWrapper = document.createElement('div');\r\n  chatWrapper.className = 'chat-wrapper';\r\n\r\n  // Left side: messages panel and input\r\n  const chatContent = document.createElement('div');\r\n  chatContent.className = 'chat-content';\r\n  const messagesPanel = document.createElement('div');\r\n  messagesPanel.id = 'messages-panel';\r\n  messagesPanel.className = 'messages-panel';\r\n  const inputContainer = document.createElement('div');\r\n  inputContainer.className = 'input-container';\r\n\r\n  // Initial setup after DOM is ready\r\n  requestAnimationFrame(() => {\r\n    (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.handleMobileLayout)(messagesPanel, inputContainer);\r\n  });\r\n\r\n  // Create emoji button\r\n  const emojiButton = document.createElement('button');\r\n  emojiButton.className = 'emoji-trigger button';\r\n  emojiButton.innerHTML = \"🙂\";\r\n  emojiButton.classList.add('emoji-button');\r\n  emojiButton.title = 'Open emoji picker';\r\n\r\n  // Add these event listeners to change the emoji on hover\r\n  emojiButton.addEventListener('mouseover', () => {\r\n    emojiButton.innerHTML = \"🙃\";\r\n  });\r\n\r\n  emojiButton.addEventListener('mouseout', () => {\r\n    emojiButton.innerHTML = \"🙂\";\r\n  });\r\n\r\n  // Setup random emoji appearance with range (5min - 10min)\r\n  (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.setupRandomEmojiAttention)(emojiButton, (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.getRandomInterval)(300000, 600000));\r\n\r\n  // Setup emoji panel toggle functionality\r\n  let emojiPanelInstance = null;\r\n  emojiButton.addEventListener('click', (e) => {\r\n    e.stopPropagation();\r\n    if (!emojiPanelInstance || !document.querySelector('.emoji-panel')) {\r\n      emojiPanelInstance = new _components_emojiPanel_js__WEBPACK_IMPORTED_MODULE_3__.EmojiPanel({\r\n        container: messagesPanel,\r\n        position: 'bottom',\r\n        emojiButton: emojiButton,\r\n        onEmojiSelect: (emoji) => {\r\n          const messageInput = document.getElementById('message-input');\r\n          if (messageInput) {\r\n            const cursorPos = messageInput.selectionStart;\r\n            const textBefore = messageInput.value.substring(0, cursorPos);\r\n            const textAfter = messageInput.value.substring(messageInput.selectionEnd);\r\n            messageInput.value = textBefore + emoji + textAfter;\r\n            const newCursorPos = cursorPos + emoji.length;\r\n            messageInput.setSelectionRange(newCursorPos, newCursorPos);\r\n            messageInput.focus();\r\n          }\r\n        },\r\n        onDestroy: () => {\r\n          emojiButton.title = 'Open emoji picker';\r\n          emojiPanelInstance = null;\r\n        }\r\n      });\r\n      emojiPanelInstance.init();\r\n      emojiButton.title = 'Close emoji picker';\r\n    } else {\r\n      emojiPanelInstance.destroy();\r\n    }\r\n  });\r\n  // Create message input\r\n  const messageInput = document.createElement('input');\r\n  messageInput.type = 'text';\r\n  messageInput.id = 'message-input';\r\n  messageInput.maxLength = 300;\r\n  messageInput.autocomplete = 'off';\r\n  // Create send button\r\n  const sendButton = document.createElement('button');\r\n  sendButton.id = 'send-button';\r\n  sendButton.className = 'button send-button';\r\n  sendButton.innerHTML = _data_icons_js__WEBPACK_IMPORTED_MODULE_1__.sendSVG;\r\n  // Append elements in order\r\n  inputContainer.appendChild(emojiButton);\r\n  inputContainer.appendChild(messageInput);\r\n  inputContainer.appendChild(sendButton);\r\n  chatContent.appendChild(messagesPanel);\r\n  chatContent.appendChild(inputContainer);\r\n  // Right side: user list\r\n  const userListContainer = document.createElement('div');\r\n  userListContainer.className = 'user-list-container';\r\n  const userList = document.createElement('div');\r\n  userList.id = 'user-list';\r\n  userListContainer.appendChild(userList);\r\n  chatWrapper.appendChild(chatContent);\r\n  chatWrapper.appendChild(userListContainer);\r\n  chatContainer.appendChild(chatWrapper);\r\n  // Maximize button\r\n  const maximizeButton = document.createElement('button');\r\n  maximizeButton.className = 'button header-button chat-maximize-button';\r\n  maximizeButton.innerHTML = _data_icons_js__WEBPACK_IMPORTED_MODULE_1__.expandSVG;\r\n  maximizeButton.addEventListener('click', _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toggleChatMaximize);\r\n  chatContainer.appendChild(maximizeButton);\r\n  // Help button next to maximize button\r\n  const helpButton = document.createElement('button');\r\n  helpButton.className = 'button header-button chat-help-button';\r\n  helpButton.innerHTML = _data_icons_js__WEBPACK_IMPORTED_MODULE_1__.helpSVG; // Replace with desired icon if available\r\n  helpButton.title = 'Show chat help';\r\n  // Declare a variable to track the help panel instance.\r\n  let helpPanelInstance = null;\r\n\r\n  helpButton.addEventListener('click', (e) => {\r\n    console.log(\"Help button clicked.\");\r\n    e.stopPropagation();\r\n\r\n    // If a help panel exists, remove it and exit.\r\n    if (helpPanelInstance && document.querySelector('.help-panel')) {\r\n      helpPanelInstance.remove();\r\n      helpButton.title = 'Show chat help';\r\n      helpPanelInstance = null;\r\n      (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.showChatAlert)('Help panel has been closed.', {\r\n        type: 'warning',\r\n        duration: 2000\r\n      });\r\n      return;\r\n    }\r\n\r\n    // Otherwise, create a new help panel.\r\n    console.log(\"Help panel does not exist. Creating help panel...\");\r\n    helpPanelInstance = new _components_helpPanel_js__WEBPACK_IMPORTED_MODULE_2__.HelpPanel({\r\n      helpButton: helpButton,\r\n      onDestroy: () => {\r\n        helpButton.title = 'Show chat help';\r\n        helpPanelInstance = null;\r\n      }\r\n    });\r\n    helpPanelInstance.init();\r\n    helpPanelInstance.show();\r\n    (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.showChatAlert)('Help panel has been opened. Press \"?\" or \"ESC\" key, or click outside to close.', {\r\n      type: 'success',\r\n      duration: 2000\r\n    });\r\n    helpButton.title = 'Hide chat help';\r\n  });\r\n\r\n  chatContainer.appendChild(helpButton);\r\n  // Toggle visibility button\r\n  const toggleButton = document.createElement('button');\r\n  toggleButton.className = 'button header-button chat-toggle-button';\r\n  toggleButton.innerHTML = _data_icons_js__WEBPACK_IMPORTED_MODULE_1__.closeSVG;\r\n  toggleButton.addEventListener('click', _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toggleChatVisibility);\r\n  chatContainer.appendChild(toggleButton);\r\n  // Draggable top area\r\n  const topArea = document.createElement('div');\r\n  topArea.className = 'chat-drag-area';\r\n  topArea.addEventListener('dblclick', _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.toggleChatVisibility);\r\n  chatContainer.appendChild(topArea);\r\n  document.body.appendChild(chatContainer);\r\n  // Restore chat state and settings\r\n  (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.restoreChatState)();\r\n  (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.createFontSizeControl)();\r\n  (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.restoreFontSize)();\r\n  requestAnimationFrame(() => {\r\n    const messagesPanel = document.getElementById('messages-panel');\r\n    const messageInput = document.getElementById('message-input');\r\n    if (messagesPanel && messageInput) {\r\n      const isMobile = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.checkIsMobile)();\r\n      if (!isMobile) {\r\n        messagesPanel.scrollTop = messagesPanel.scrollHeight;\r\n      } else {\r\n        setTimeout(() => {\r\n          messagesPanel.scrollTop = messagesPanel.scrollHeight;\r\n        }, 2000);\r\n      }\r\n      messageInput.value = ''; // Clear input field on load\r\n      // Pass the input element and messages panel into the helper functions.\r\n      (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.createLengthPopup)(messagesPanel);\r\n      (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.initChatLengthPopupEvents)(messageInput);\r\n    }\r\n  });\r\n}\r\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/chat/chatUI.js?");

/***/ }),

/***/ "./src/components/chatUsernameColorsPanel.js":
/*!***************************************************!*\
  !*** ./src/components/chatUsernameColorsPanel.js ***!
  \***************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   exportUsernameColors: () => (/* binding */ exportUsernameColors),\n/* harmony export */   importUsernameColors: () => (/* binding */ importUsernameColors),\n/* harmony export */   openUsernameColors: () => (/* binding */ openUsernameColors)\n/* harmony export */ });\n/* harmony import */ var _helpers_helpers__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../helpers/helpers */ \"./src/helpers/helpers.js\");\n\r\n\r\n// Centralized storage wrapper.\r\nconst storageKey = 'usernameColors';\r\nconst storageWrapper = {\r\n  get: (storage) => {\r\n    try {\r\n      const stored = storage.getItem(storageKey);\r\n      return stored ? JSON.parse(stored) : {};\r\n    } catch (e) {\r\n      console.error(\r\n        `Error parsing ${storage === sessionStorage ? 'sessionStorage' : 'localStorage'} data:`,\r\n        e\r\n      );\r\n      return {};\r\n    }\r\n  },\r\n  set: (storage, data) => {\r\n    try {\r\n      storage.setItem(storageKey, JSON.stringify(data));\r\n      return true;\r\n    } catch (e) {\r\n      console.error(\r\n        `Error saving data to ${storage === sessionStorage ? 'sessionStorage' : 'localStorage'}:`,\r\n        e\r\n      );\r\n      return false;\r\n    }\r\n  }\r\n};\r\n\r\nconst storageOps = {\r\n  getColors: () => storageWrapper.get(sessionStorage),\r\n  saveColor: (username, color) => {\r\n    const localColors = storageWrapper.get(localStorage);\r\n    localColors[username] = color;\r\n    return storageWrapper.set(localStorage, localColors);\r\n  },\r\n  removeColor: (username) => {\r\n    const localColors = storageWrapper.get(localStorage);\r\n    if (username in localColors) {\r\n      delete localColors[username];\r\n      return storageWrapper.set(localStorage, localColors);\r\n    }\r\n    return false;\r\n  },\r\n  isColorSaved: (username) => {\r\n    const localColors = storageWrapper.get(localStorage);\r\n    return username in localColors;\r\n  }\r\n};\r\n\r\n// DOM element creation helper.\r\nconst createElement = (tag, className, attributes = {}) => {\r\n  const element = document.createElement(tag);\r\n  if (className) element.className = className;\r\n  Object.entries(attributes).forEach(([key, value]) => {\r\n    if (key === 'text') {\r\n      element.textContent = value;\r\n    } else if (key === 'html') {\r\n      element.innerHTML = value;\r\n    } else {\r\n      element.setAttribute(key, value);\r\n    }\r\n  });\r\n  return element;\r\n};\r\n\r\n// Helper: append alpha to hex.\r\nfunction hexWithAlpha(hex, alpha) {\r\n  const alphaHex = Math.round(alpha * 255).toString(16).padStart(2, '0');\r\n  return hex.length === 7 ? hex + alphaHex : hex.slice(0, 7) + alphaHex;\r\n}\r\n\r\n// Create remove (X) SVG icon.\r\nconst createRemoveSVG = () => {\r\n  const svg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\r\n  svg.setAttribute(\"viewBox\", \"0 0 24 24\");\r\n  svg.setAttribute(\"width\", \"12\");\r\n  svg.setAttribute(\"height\", \"12\");\r\n  svg.setAttribute(\"fill\", \"none\");\r\n  svg.setAttribute(\"stroke\", \"currentColor\");\r\n  svg.setAttribute(\"stroke-width\", \"2\");\r\n  svg.setAttribute(\"stroke-linecap\", \"round\");\r\n  svg.setAttribute(\"stroke-linejoin\", \"round\");\r\n  svg.classList.add(\"remove-icon\");\r\n\r\n  ['M18 6L6 18', 'M6 6L18 18'].forEach(d => {\r\n    const path = document.createElementNS(\"http://www.w3.org/2000/svg\", \"path\");\r\n    path.setAttribute(\"d\", d);\r\n    svg.appendChild(path);\r\n  });\r\n  return svg;\r\n};\r\n\r\n// Update styling for label and color box.\r\nconst updateStyles = (label, colorBox, color) => {\r\n  label.style.color = color;\r\n  Object.assign(colorBox.style, {\r\n    backgroundColor: hexWithAlpha(color, 0.4),\r\n    color\r\n  });\r\n  colorBox.textContent = color;\r\n};\r\n\r\n// Validate hex color (#FFFFFF format)\r\nconst isValidHex = (hex) => /^#[0-9A-Fa-f]{6}$/.test(hex);\r\n\r\n// The main exported function.\r\nconst openUsernameColors = () => {\r\n  // Prevent duplicate container creation.\r\n  const existingContainer = document.querySelector('.chat-username-color-picker');\r\n  if (existingContainer) {\r\n    return existingContainer;\r\n  }\r\n\r\n  const sessionColors = storageOps.getColors();\r\n  let savedBlock = null;\r\n  let longPressTimer = null;\r\n  let currentEntry = null;\r\n  const longPressDuration = 500;\r\n\r\n  // Create container and blocks.\r\n  const container = createElement('div', 'chat-username-color-picker', { html: '<h2>Username Colors</h2>' });\r\n  const generatedBlock = createElement(\r\n    'div',\r\n    'generated-username-colors',\r\n    { html: '<h3>Generated Colors <span class=\"counter\">0</span></h3>' }\r\n  );\r\n\r\n  // Create saved colors block if needed.\r\n  const createSavedBlock = () => {\r\n    if (!savedBlock) {\r\n      savedBlock = createElement(\r\n        'div',\r\n        'saved-username-colors',\r\n        { html: '<h3>Saved Colors <span class=\"counter\">0</span></h3>' }\r\n      );\r\n      const header = container.querySelector('h2');\r\n      header.nextSibling ? container.insertBefore(savedBlock, header.nextSibling) : container.appendChild(savedBlock);\r\n    }\r\n  };\r\n\r\n  // New helper to update header counters.\r\n  function updateCounters() {\r\n    if (generatedBlock) {\r\n      const count = generatedBlock.querySelectorAll('.username-entry').length;\r\n      const counterSpan = generatedBlock.querySelector('h3 .counter');\r\n      if (counterSpan) { counterSpan.textContent = count; }\r\n    }\r\n    if (savedBlock) {\r\n      const count = savedBlock.querySelectorAll('.username-entry').length;\r\n      const counterSpan = savedBlock.querySelector('h3 .counter');\r\n      if (counterSpan) { counterSpan.textContent = count; }\r\n    }\r\n  }\r\n\r\n  const updateGeneratedBlockStatus = () => {\r\n    generatedBlock.querySelectorAll('.username-entry').forEach(entry => {\r\n      const username = entry.querySelector('.user-label').textContent;\r\n      entry.classList.toggle('disabled-entry', storageOps.isColorSaved(username));\r\n    });\r\n    updateCounters();\r\n  };\r\n\r\n  // Create an entry element.\r\n  const createEntry = (username, color, isSaved = false) => {\r\n    const entry = createElement('div', 'username-entry');\r\n    const label = createElement('div', 'user-label', { text: username });\r\n    const colorBox = createElement('div', 'color-box');\r\n    updateStyles(label, colorBox, color);\r\n    const colorInput = createElement('input', null, { type: 'color', value: color });\r\n\r\n    // We keep a reference to the input element on the entry.\r\n    entry._colorInput = colorInput;\r\n\r\n    entry.append(label, colorBox, colorInput);\r\n\r\n    if (isSaved) {\r\n      // Create remove button on initialization.\r\n      const removeBtn = createOrUpdateRemoveButton(entry, username, color, () => {\r\n        entry.remove();\r\n        updateGeneratedBlockStatus();\r\n        if (savedBlock && !savedBlock.querySelector('.username-entry')) {\r\n          savedBlock.remove();\r\n          savedBlock = null;\r\n        }\r\n      });\r\n      // Save a reference for later update.\r\n      entry._removeBtn = removeBtn;\r\n    }\r\n\r\n    const debouncedUpdate = (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.debounce)(() => {\r\n      const newColor = colorInput.value;\r\n      updateStyles(label, colorBox, newColor);\r\n      if (!isSaved) {\r\n        sessionColors[username] = newColor;\r\n        storageWrapper.set(sessionStorage, sessionColors);\r\n        storageOps.saveColor(username, newColor);\r\n        createSavedBlock();\r\n        renderSavedBlock();\r\n        updateGeneratedBlockStatus();\r\n      } else {\r\n        storageOps.saveColor(username, newColor);\r\n        // Update or recreate the remove button.\r\n        if (!entry._removeBtn || !entry.contains(entry._removeBtn)) {\r\n          entry._removeBtn = createOrUpdateRemoveButton(entry, username, newColor, () => {\r\n            entry.remove();\r\n            updateGeneratedBlockStatus();\r\n            if (savedBlock && !savedBlock.querySelector('.username-entry')) {\r\n              savedBlock.remove();\r\n              savedBlock = null;\r\n            }\r\n          });\r\n        } else {\r\n          Object.assign(entry._removeBtn.style, {\r\n            backgroundColor: hexWithAlpha(newColor, 0.4),\r\n            color: newColor\r\n          });\r\n        }\r\n      }\r\n    }, 1000);\r\n\r\n    colorInput.addEventListener('input', debouncedUpdate);\r\n    return entry;\r\n  };\r\n\r\n  // Render saved colors block.\r\n  const renderSavedBlock = () => {\r\n    if (!savedBlock) return;\r\n    const localColors = storageWrapper.get(localStorage);\r\n    savedBlock.innerHTML = '<h3>Saved Colors <span class=\"counter\">0</span></h3>';\r\n    Object.entries(localColors).forEach(([username, color]) => {\r\n      const entry = createEntry(username, color, true);\r\n      savedBlock.appendChild(entry);\r\n    });\r\n    if (!savedBlock.querySelector('.username-entry')) {\r\n      savedBlock.remove();\r\n      savedBlock = null;\r\n    }\r\n    updateCounters();\r\n  };\r\n\r\n  // Render generated colors.\r\n  Object.entries(sessionColors).forEach(([username, color]) => {\r\n    const entry = createEntry(username, color);\r\n    generatedBlock.appendChild(entry);\r\n  });\r\n  updateCounters();\r\n  createSavedBlock();\r\n  renderSavedBlock();\r\n  updateGeneratedBlockStatus();\r\n\r\n  container.appendChild(generatedBlock);\r\n  document.body.appendChild(container);\r\n\r\n  (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.adjustVisibility)(container, 'show', 1);\r\n\r\n  // Hide container on outside click.\r\n  const handleOutsideClick = (e) => {\r\n    if (!container.contains(e.target)) {\r\n      (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.adjustVisibility)(container, 'hide', 0);\r\n      document.removeEventListener('click', handleOutsideClick, true);\r\n    }\r\n  };\r\n  document.addEventListener('click', handleOutsideClick, true);\r\n\r\n  // Delegate click events on the container.\r\n  container.addEventListener('click', (e) => {\r\n    // Handle remove button clicks.\r\n    const removeBtn = e.target.closest('.remove-btn');\r\n    if (removeBtn) {\r\n      const entry = removeBtn.closest('.username-entry');\r\n      if (!entry) return;\r\n      const username = entry.querySelector('.user-label').textContent;\r\n      storageOps.removeColor(username);\r\n      entry.remove();\r\n      updateGeneratedBlockStatus();\r\n      if (savedBlock && !savedBlock.querySelector('.username-entry')) {\r\n        savedBlock.remove();\r\n        savedBlock = null;\r\n      }\r\n      return;\r\n    }\r\n\r\n    // Handle color box click.\r\n    const colorBox = e.target.closest('.color-box');\r\n    if (colorBox) {\r\n      const entry = colorBox.closest('.username-entry');\r\n      if (!entry || entry.classList.contains('disabled-entry') || entry._customInputActive) return;\r\n      // Trigger the color input.\r\n      if (entry._colorInput) {\r\n        entry._colorInput.click();\r\n      }\r\n    }\r\n  });\r\n\r\n  // Delegate pointer events for long press on color boxes.\r\n  container.addEventListener('pointerdown', (e) => {\r\n    const colorBox = e.target.closest('.color-box');\r\n    if (!colorBox) return;\r\n    const entry = colorBox.closest('.username-entry');\r\n    if (!entry || entry.classList.contains('disabled-entry')) return;\r\n    // Start long press timer.\r\n    longPressTimer = setTimeout(() => {\r\n      entry._isLongPress = true;\r\n      showCustomInput(entry);\r\n    }, longPressDuration);\r\n    // Save the current entry for pointerup/leave events.\r\n    currentEntry = entry;\r\n  });\r\n\r\n  container.addEventListener('pointerup', clearLongPress);\r\n  container.addEventListener('pointerleave', clearLongPress);\r\n  container.addEventListener('pointercancel', clearLongPress);\r\n\r\n  function clearLongPress() {\r\n    clearTimeout(longPressTimer);\r\n    if (currentEntry) {\r\n      currentEntry._isLongPress = false;\r\n      currentEntry = null;\r\n    }\r\n  }\r\n\r\n  // Delegated function to show custom input.\r\n  function showCustomInput(entry) {\r\n    if (entry.querySelector('.custom-color-input')) return;\r\n    entry._customInputActive = true;\r\n    const customInputContainer = createElement('div', 'custom-color-input');\r\n    const hexInput = createElement('input', 'hex-input', { type: 'text', placeholder: 'Enter hex color' });\r\n    const confirmBtn = createElement('button', 'confirm-btn', { text: 'Confirm' });\r\n    customInputContainer.append(hexInput, confirmBtn);\r\n    entry.appendChild(customInputContainer);\r\n    hexInput.focus();\r\n    const handleConfirm = () => {\r\n      const hexValue = hexInput.value.trim();\r\n      if (isValidHex(hexValue)) {\r\n        if (entry._colorInput) {\r\n          entry._colorInput.value = hexValue;\r\n          entry._colorInput.dispatchEvent(new Event('input', { bubbles: true }));\r\n        }\r\n        entry.removeChild(customInputContainer);\r\n        entry._customInputActive = false;\r\n      } else {\r\n        alert('Invalid hex color');\r\n      }\r\n    };\r\n    confirmBtn.addEventListener('click', (e) => {\r\n      e.stopPropagation();\r\n      handleConfirm();\r\n    });\r\n    hexInput.addEventListener('keypress', (e) => {\r\n      if (e.key === 'Enter') {\r\n        e.stopPropagation();\r\n        handleConfirm();\r\n      }\r\n    });\r\n    customInputContainer.addEventListener('click', (e) => e.stopPropagation());\r\n  }\r\n\r\n  return container;\r\n};\r\n\r\n// Helper to create or update the remove button for an entry.\r\nconst createOrUpdateRemoveButton = (entry, username, color, updateCb) => {\r\n  let removeBtn = entry.querySelector('.remove-btn');\r\n  if (removeBtn) entry.removeChild(removeBtn);\r\n  removeBtn = createElement('div', 'remove-btn');\r\n  removeBtn.appendChild(createRemoveSVG());\r\n  removeBtn.title = \"Remove saved color\";\r\n  Object.assign(removeBtn.style, {\r\n    backgroundColor: hexWithAlpha(color, 0.4),\r\n    color\r\n  });\r\n  removeBtn.addEventListener('click', (e) => {\r\n    e.stopPropagation();\r\n    storageOps.removeColor(username);\r\n    entry.removeChild(removeBtn);\r\n    if (typeof updateCb === 'function') updateCb();\r\n  });\r\n  entry.appendChild(removeBtn);\r\n  return removeBtn;\r\n};\r\n\r\n// Helper to export username colors to a JSON file.\r\nfunction exportUsernameColors() {\r\n  const usernameColors = localStorage.getItem('usernameColors');\r\n  if (!usernameColors) {\r\n    (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.showChatAlert)('No username colors found to export', { type: 'error', duration: 2000 });\r\n    return;\r\n  }\r\n  // Parse and stringify with indentation\r\n  const formattedJson = JSON.stringify(JSON.parse(usernameColors), null, 2);\r\n  const blob = new Blob([formattedJson], { type: 'application/json' });\r\n  const url = URL.createObjectURL(blob);\r\n  const a = document.createElement('a');\r\n  a.href = url;\r\n  a.download = 'usernameColors.json';\r\n  a.click();\r\n  URL.revokeObjectURL(url);\r\n  (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.showChatAlert)('Username colors exported as JSON file', { type: 'info', duration: 2000 });\r\n}\r\n\r\n// Helper to import username colors from a JSON file.\r\nfunction importUsernameColors() {\r\n  const input = document.createElement('input');\r\n  input.type = 'file';\r\n  input.accept = 'application/json';\r\n  input.style.display = 'none';\r\n  document.body.appendChild(input);\r\n  input.addEventListener('change', (event) => {\r\n    const file = event.target.files[0];\r\n    if (!file) {\r\n      (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.showChatAlert)('No file selected', { type: 'error', duration: 2000 });\r\n      return;\r\n    }\r\n    const reader = new FileReader();\r\n    reader.onload = (e) => {\r\n      try {\r\n        const jsonData = JSON.parse(e.target.result);\r\n        localStorage.setItem('usernameColors', JSON.stringify(jsonData));\r\n        (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.showChatAlert)('Username colors imported successfully', { type: 'info', duration: 2000 });\r\n      } catch (err) {\r\n        (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.showChatAlert)('Invalid JSON file', { type: 'error', duration: 2000 });\r\n      }\r\n    };\r\n    reader.readAsText(file);\r\n  });\r\n  input.click();\r\n  document.body.removeChild(input);\r\n}\n\n//# sourceURL=webpack://tampermonkey-script/./src/components/chatUsernameColorsPanel.js?");

/***/ }),

/***/ "./src/components/emojiPanel.js":
/*!**************************************!*\
  !*** ./src/components/emojiPanel.js ***!
  \**************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   EmojiPanel: () => (/* binding */ EmojiPanel)\n/* harmony export */ });\n/* harmony import */ var _data_emojiData_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../data/emojiData.js */ \"./src/data/emojiData.js\");\n/* harmony import */ var _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../helpers/helpers.js */ \"./src/helpers/helpers.js\");\n\r\n\r\n\r\n\r\n// Create a single global shortcut handler\r\nconst setupGlobalEmojiShortcut = (() => {\r\n  let isSetup = false;\r\n\r\n  return function () {\r\n    if (!isSetup) {\r\n      document.addEventListener('keydown', (e) => {\r\n        if (e.ctrlKey && e.code === 'Semicolon') {\r\n          e.preventDefault();\r\n          if (!document.querySelector('.emoji-panel')) {\r\n            // Initialize or show the emoji panel with a callback to insert emoji\r\n            new EmojiPanel({\r\n              onEmojiSelect: (emoji) => {\r\n                const input = document.getElementById('message-input');\r\n                if (input) {\r\n                  input.value += emoji;\r\n                  // Optionally, dispatch an input event if your application needs it\r\n                  input.dispatchEvent(new Event('input', { bubbles: true }));\r\n                  input.focus(); // Add this line to restore focus\r\n                }\r\n              }\r\n            }).show();\r\n          }\r\n        }\r\n      });\r\n      isSetup = true;\r\n    }\r\n  };\r\n})();\r\n\r\n// Call this function immediately to set up the shortcut\r\nsetupGlobalEmojiShortcut();\r\n\r\n\r\nclass EmojiPanel {\r\n  static instance = null;\r\n\r\n  constructor(options = {}) {\r\n    if (EmojiPanel.instance) {\r\n      return EmojiPanel.instance;\r\n    }\r\n    EmojiPanel.instance = this;\r\n    this.options = {\r\n      onEmojiSelect: options.onEmojiSelect || (() => { }),\r\n      container: options.container || document.getElementById('messages-panel') || document.body,\r\n      position: options.position || 'bottom',\r\n      onDestroy: options.onDestroy,\r\n      emojiButton: options.emojiButton,\r\n    };\r\n\r\n    // DOM elements\r\n    this.container = null;\r\n    this.searchInput = null;\r\n    this.emojiContainer = null;\r\n    this.categoryNav = null;\r\n    this.infoPanel = null;\r\n    this.infoIcon = null;\r\n    this.infoKeywords = null;\r\n    this.languageSelect = null;\r\n\r\n    // Category definitions with icons\r\n    this.categories = {\r\n      recent: { icon: '🕒' },\r\n      smileys: { icon: '😊' },\r\n      nature: { icon: '🦊' },\r\n      food: { icon: '🍔' },\r\n      activities: { icon: '⚽' },\r\n      travel: { icon: '✈️' },\r\n      objects: { icon: '💡' },\r\n      symbols: { icon: '💕' },\r\n      flags: { icon: '🎌' }\r\n    };\r\n\r\n    // Localized category names\r\n    this.categoryLabels = {\r\n      en: {\r\n        recent: 'Recently Used',\r\n        smileys: 'Smileys & People',\r\n        nature: 'Animals & Nature',\r\n        food: 'Food & Drink',\r\n        activities: 'Activities',\r\n        travel: 'Travel & Places',\r\n        objects: 'Objects',\r\n        symbols: 'Symbols',\r\n        flags: 'Flags'\r\n      },\r\n      ru: {\r\n        recent: 'Недавно использованные',\r\n        smileys: 'Смайлы и люди',\r\n        nature: 'Животные и природа',\r\n        food: 'Еда и напитки',\r\n        activities: 'Активности',\r\n        travel: 'Путешествия и места',\r\n        objects: 'Объекты',\r\n        symbols: 'Символы',\r\n        flags: 'Флаги'\r\n      }\r\n    };\r\n\r\n    // UI Labels for static text elements\r\n    this.uiLabels = {\r\n      en: {\r\n        searchResults: 'Search Results'\r\n      },\r\n      ru: {\r\n        searchResults: 'Результаты поиска'\r\n      }\r\n    };\r\n\r\n    // Emoji data and keywords\r\n    this.emojiData = _data_emojiData_js__WEBPACK_IMPORTED_MODULE_0__.emojiData;\r\n    this.emojiKeywords = _data_emojiData_js__WEBPACK_IMPORTED_MODULE_0__.emojiKeywords;\r\n\r\n    // Recently used emojis (loaded from localStorage)\r\n    this.recentEmojis = this.loadRecentEmojis();\r\n\r\n    // Infinite scroll settings\r\n    this.batchSize = 50;\r\n    this.loadedIndices = {};\r\n    this.categorySections = {};\r\n\r\n    // Retrieve current language from localStorage (default to 'en')\r\n    this.currentLanguage = localStorage.getItem('emojiPanelLanguage') || 'en';\r\n  }\r\n\r\n  /** Initialize the emoji panel */\r\n  init() {\r\n    this.createPanel();\r\n    this.bindEvents();\r\n    this.loadAllEmojis();\r\n    return this;\r\n  }\r\n\r\n  /** Create the HTML structure of the emoji panel */\r\n  createPanel() {\r\n    if (document.querySelector('.emoji-panel')) {\r\n      return;\r\n    }\r\n    this.container = document.createElement('div');\r\n    this.container.className = 'emoji-panel';\r\n\r\n    // Search container\r\n    const searchContainer = document.createElement('div');\r\n    searchContainer.className = 'emoji-search-container';\r\n    this.searchInput = document.createElement('input');\r\n    this.searchInput.type = 'search';\r\n    this.searchInput.className = 'emoji-search';\r\n    searchContainer.appendChild(this.searchInput);\r\n\r\n    // Category navigation\r\n    this.categoryNav = document.createElement('div');\r\n    this.categoryNav.className = 'emoji-categories';\r\n    Object.entries(this.categories).forEach(([key, category]) => {\r\n      const categoryBtn = document.createElement('button');\r\n      categoryBtn.className = 'emoji-category-btn';\r\n      categoryBtn.dataset.category = key;\r\n      categoryBtn.innerHTML = category.icon;\r\n      categoryBtn.title = this.getLocalizedCategoryName(key);\r\n      this.categoryNav.appendChild(categoryBtn);\r\n    });\r\n\r\n    // Emoji grid container\r\n    this.emojiContainer = document.createElement('div');\r\n    this.emojiContainer.className = 'emoji-container';\r\n\r\n    // Info panel\r\n    this.infoPanel = document.createElement('div');\r\n    this.infoPanel.className = 'emoji-info-panel';\r\n    this.infoIcon = document.createElement('span');\r\n    this.infoIcon.className = 'emoji-info-icon';\r\n    this.infoKeywords = document.createElement('span');\r\n    this.infoKeywords.className = 'emoji-info-keywords';\r\n    this.infoPanel.appendChild(this.infoIcon);\r\n    this.infoPanel.appendChild(this.infoKeywords);\r\n\r\n    // Language selector\r\n    this.languageSelect = document.createElement('select');\r\n    this.languageSelect.className = 'emoji-language-select';\r\n    this.languageSelect.innerHTML = `\r\n      <option value=\"en\">EN</option>\r\n      <option value=\"ru\">RU</option>\r\n    `;\r\n    this.languageSelect.value = this.currentLanguage;\r\n\r\n    // Footer to hold info panel and language selector\r\n    const footer = document.createElement('div');\r\n    footer.className = 'emoji-footer';\r\n    footer.appendChild(this.infoPanel);\r\n    footer.appendChild(this.languageSelect);\r\n\r\n    // Assemble the panel\r\n    this.container.appendChild(searchContainer);\r\n    this.container.appendChild(this.categoryNav);\r\n    this.container.appendChild(this.emojiContainer);\r\n    this.container.appendChild(footer);\r\n    this.options.container.appendChild(this.container);\r\n    // Fade in the panel\r\n    (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.adjustVisibility)(this.container, 'show', '1');\r\n\r\n    // Perform mobile check and focus only when the panel is created\r\n    if (!(0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.checkIsMobile)()) {\r\n      this.searchInput.focus();\r\n    }\r\n  }\r\n\r\n  /** Load initial batch of emojis for all categories */\r\n  loadAllEmojis() {\r\n    if (!this.emojiContainer) return; // Exit early if container is missing\r\n    this.emojiContainer.innerHTML = '';\r\n    this.loadedIndices = {};\r\n    this.categorySections = {};\r\n    Object.keys(this.categories).forEach(category => {\r\n      const section = document.createElement('div');\r\n      section.className = 'emoji-category-section';\r\n      section.id = `emoji-section-${category}`;\r\n      const header = document.createElement('div');\r\n      header.className = 'emoji-category-header';\r\n      header.textContent = this.getLocalizedCategoryName(category);\r\n      section.appendChild(header);\r\n      const emojiList = document.createElement('div');\r\n      emojiList.className = 'emoji-list';\r\n      section.appendChild(emojiList);\r\n      this.emojiContainer.appendChild(section);\r\n      this.loadedIndices[category] = 0;\r\n      this.categorySections[category] = { section, emojiList, header };\r\n      this.loadMoreEmojisForCategory(category);\r\n    });\r\n  }\r\n\r\n  /** Load more emojis for a specific category */\r\n  loadMoreEmojisForCategory(category) {\r\n    const data = category === 'recent' ? this.recentEmojis : (this.emojiData[category] || []);\r\n    const start = this.loadedIndices[category];\r\n    const batch = data.slice(start, start + this.batchSize);\r\n    if (!batch.length) return;\r\n    const container = this.categorySections[category].emojiList;\r\n    batch.forEach(emoji => {\r\n      const button = document.createElement('button');\r\n      button.className = 'emoji-btn';\r\n      button.textContent = emoji;\r\n      button.addEventListener('mouseenter', () => this.updateInfoPanel(emoji));\r\n      button.addEventListener('mouseleave', () => this.clearInfoPanel());\r\n      button.addEventListener('click', (e) => {\r\n        e.stopPropagation();\r\n        if (e.shiftKey && category === 'recent') {\r\n          e.preventDefault();\r\n          this.removeFromRecent(emoji);\r\n        } else {\r\n          this.addToRecent(emoji);\r\n          this.options.onEmojiSelect(emoji);\r\n          if (!e.ctrlKey) {\r\n            this.destroy();\r\n          } else {\r\n            this.searchInput.focus();\r\n          }\r\n        }\r\n      });\r\n      container.appendChild(button);\r\n    });\r\n    this.loadedIndices[category] += batch.length;\r\n  }\r\n\r\n  /** Search for emojis based on a search term */\r\n  searchEmojis(searchTerm) {\r\n    if (!this.emojiContainer) return; // Exit early if container is missing\r\n    this.emojiContainer.innerHTML = '';\r\n    const section = document.createElement('div');\r\n    section.className = 'emoji-category-section';\r\n    const header = document.createElement('div');\r\n    header.className = 'emoji-category-header';\r\n    header.textContent = this.uiLabels[this.currentLanguage].searchResults;\r\n    section.appendChild(header);\r\n    const emojiList = document.createElement('div');\r\n    emojiList.className = 'emoji-list';\r\n    section.appendChild(emojiList);\r\n    const results = [];\r\n    Object.keys(this.emojiData).forEach(category => {\r\n      const emojis = this.emojiData[category] || [];\r\n      emojis.forEach(emoji => {\r\n        const keywordsObj = this.emojiKeywords[emoji] || {};\r\n        const allKeywords = Object.values(keywordsObj).flat();\r\n        if (\r\n          emoji.includes(searchTerm) ||\r\n          allKeywords.some(keyword => keyword.toLowerCase().includes(searchTerm))\r\n        ) {\r\n          results.push(emoji);\r\n        }\r\n      });\r\n    });\r\n    results.forEach(emoji => {\r\n      const button = document.createElement('button');\r\n      button.className = 'emoji-btn';\r\n      button.textContent = emoji;\r\n      button.addEventListener('mouseenter', () => this.updateInfoPanel(emoji));\r\n      button.addEventListener('mouseleave', () => this.clearInfoPanel());\r\n      button.addEventListener('click', (e) => {\r\n        e.stopPropagation();\r\n        this.addToRecent(emoji);\r\n        this.options.onEmojiSelect(emoji);\r\n        if (!e.ctrlKey) {\r\n          this.destroy();\r\n        } else {\r\n          this.searchInput.focus();\r\n        }\r\n      });\r\n      emojiList.appendChild(button);\r\n    });\r\n    this.emojiContainer.appendChild(section);\r\n  }\r\n\r\n  /** Bind event listeners for the emoji panel */\r\n  bindEvents() {\r\n    // Close panel on clicking outside\r\n    this._documentClickHandler = (e) => {\r\n      if (!this.container.contains(e.target)) {\r\n        this.destroy();\r\n      }\r\n    };\r\n    document.addEventListener('click', this._documentClickHandler);\r\n\r\n    // Close panel on Escape key\r\n    this._emojiKeydownHandler = (e) => {\r\n      if (e.key === 'Escape') {\r\n        this.destroy();\r\n      }\r\n    };\r\n    document.addEventListener('keydown', this._emojiKeydownHandler);\r\n\r\n    // Handle 'q' key for closing panel\r\n    this._qKeydownHandler = (e) => {\r\n      if (e.code === 'KeyQ') {\r\n        if (document.activeElement === this.searchInput) {\r\n          const now = Date.now();\r\n          if (this._lastQPressTime && (now - this._lastQPressTime < 500)) {\r\n            e.preventDefault();\r\n            this.destroy();\r\n            this._lastQPressTime = 0;\r\n          } else {\r\n            this._lastQPressTime = now;\r\n          }\r\n        } else {\r\n          e.preventDefault();\r\n          this.destroy();\r\n        }\r\n      }\r\n    };\r\n    document.addEventListener('keydown', this._qKeydownHandler);\r\n\r\n    // Update view on search input change\r\n    this.searchInput.addEventListener('input', (e) => {\r\n      const searchTerm = e.target.value.trim().toLowerCase();\r\n      if (searchTerm) {\r\n        this.searchEmojis(searchTerm);\r\n        this.emojiContainer.classList.add('search-active');\r\n      } else {\r\n        this.loadAllEmojis();\r\n        this.emojiContainer.classList.remove('search-active');\r\n      }\r\n    });\r\n\r\n    // Handle Enter key in search input\r\n    this.searchInput.addEventListener('keydown', (e) => {\r\n      if (e.key === 'Enter') {\r\n        e.preventDefault();\r\n\r\n        // Ensure both elements exist before proceeding\r\n        if (!this.searchInput || !this.emojiContainer) return;\r\n\r\n        if (this.emojiContainer.classList.contains('search-active')) {\r\n          const firstEmojiBtn = this.emojiContainer?.querySelector('.emoji-btn');\r\n\r\n          if (firstEmojiBtn) {\r\n            const clickEvent = new MouseEvent('click', {\r\n              bubbles: true,\r\n              cancelable: true,\r\n              ctrlKey: e.ctrlKey\r\n            });\r\n            firstEmojiBtn.dispatchEvent(clickEvent);\r\n\r\n            // Ensure emojiContainer still exists before modifying it\r\n            if (this.emojiContainer) {\r\n              this.emojiContainer.classList.remove('search-active');\r\n              this.loadAllEmojis(); // Only if emojiContainer is still valid\r\n            }\r\n\r\n            // Ensure the search input exists before modifying it\r\n            if (this.searchInput) {\r\n              this.searchInput.value = '';\r\n            }\r\n\r\n            if (!e.ctrlKey) {\r\n              this.destroy();\r\n            } else if (this.searchInput) {\r\n              this.searchInput.focus();\r\n            }\r\n          }\r\n        }\r\n      }\r\n    });\r\n\r\n    // Category navigation clicks\r\n    this.categoryNav.addEventListener('click', (e) => {\r\n      const btn = e.target.closest('.emoji-category-btn');\r\n      if (btn) {\r\n        const category = btn.dataset.category;\r\n        const section = document.getElementById(`emoji-section-${category}`);\r\n        if (section && this.emojiContainer) {\r\n          // Add smooth scrolling behavior\r\n          this.emojiContainer.style.scrollBehavior = 'smooth';\r\n\r\n          // Instead of using scrollIntoView, manually set the scrollTop\r\n          // of the emoji container to the offsetTop of the section\r\n          this.emojiContainer.scrollTop = section.offsetTop - this.emojiContainer.offsetTop;\r\n\r\n          // Prevent default behavior to avoid any parent scrolling\r\n          e.preventDefault();\r\n          e.stopPropagation();\r\n        }\r\n      }\r\n    });\r\n\r\n    // Infinite scroll handler\r\n    this.emojiContainer.addEventListener('scroll', () => this.handleScroll());\r\n\r\n    // Language change handler\r\n    this.languageSelect.addEventListener('change', (e) => {\r\n      this.currentLanguage = e.target.value;\r\n      localStorage.setItem('emojiPanelLanguage', this.currentLanguage);\r\n      const currentEmoji = this.infoIcon.textContent;\r\n      if (currentEmoji) {\r\n        this.updateInfoPanel(currentEmoji);\r\n      }\r\n      this.updateCategoryLabels();\r\n      if (this.searchInput.value.trim()) {\r\n        this.searchEmojis(this.searchInput.value.trim().toLowerCase());\r\n      }\r\n    });\r\n\r\n    // Prevent closing when clicking inside panel\r\n    this.container.addEventListener('click', (e) => {\r\n      e.stopPropagation();\r\n    });\r\n  }\r\n\r\n  /** Handle scroll events for infinite scrolling and category highlighting */\r\n  handleScroll() {\r\n    Object.keys(this.categorySections).forEach(category => {\r\n      const { section } = this.categorySections[category];\r\n      const sectionRect = section.getBoundingClientRect();\r\n      const containerRect = this.emojiContainer.getBoundingClientRect();\r\n      if (sectionRect.bottom < containerRect.bottom + 100) {\r\n        this.loadMoreEmojisForCategory(category);\r\n      }\r\n    });\r\n    let activeCategory = null;\r\n    let minDistance = Infinity;\r\n    Object.keys(this.categorySections).forEach(category => {\r\n      const headerRect = this.categorySections[category].header.getBoundingClientRect();\r\n      const containerRect = this.emojiContainer.getBoundingClientRect();\r\n      const distance = Math.abs(headerRect.top - containerRect.top);\r\n      if (headerRect.top <= containerRect.top + 10 && distance < minDistance) {\r\n        minDistance = distance;\r\n        activeCategory = category;\r\n      }\r\n    });\r\n    if (activeCategory) {\r\n      this.highlightCategory(activeCategory);\r\n    }\r\n  }\r\n\r\n  /** Update category labels based on current language */\r\n  updateCategoryLabels() {\r\n    Object.keys(this.categories).forEach(category => {\r\n      const localizedName = this.getLocalizedCategoryName(category);\r\n      const btn = this.categoryNav.querySelector(`[data-category=\"${category}\"]`);\r\n      if (btn) btn.title = localizedName;\r\n      if (this.categorySections[category] && this.categorySections[category].header) {\r\n        this.categorySections[category].header.textContent = localizedName;\r\n      }\r\n    });\r\n  }\r\n\r\n  /** Update the info panel with emoji and keywords */\r\n  updateInfoPanel(emoji) {\r\n    // Check if the info panel elements exist before trying to update them\r\n    if (!this.infoIcon || !this.infoKeywords) return;\r\n\r\n    this.infoIcon.textContent = emoji;\r\n    const keywordsObj = this.emojiKeywords[emoji] || {};\r\n    const keywords = keywordsObj[this.currentLanguage] || [];\r\n    this.infoKeywords.textContent = keywords.join(', ');\r\n  }\r\n\r\n  /** Clear the info panel */\r\n  clearInfoPanel() {\r\n    // Check if the info panel elements exist before trying to clear them\r\n    if (!this.infoIcon || !this.infoKeywords) return;\r\n\r\n    this.infoIcon.textContent = '';\r\n    this.infoKeywords.textContent = '';\r\n  }\r\n\r\n  /** Add an emoji to the recent list and refresh the recent section */\r\n  addToRecent(emoji) {\r\n    this.recentEmojis = [\r\n      emoji,\r\n      ...this.recentEmojis.filter(e => e !== emoji)\r\n    ].slice(0, 25);\r\n    this.saveRecentEmojis();\r\n    if (this.categorySections['recent']) {\r\n      const recentList = this.categorySections['recent'].emojiList;\r\n      recentList.innerHTML = '';\r\n      this.loadedIndices['recent'] = 0;\r\n      this.loadMoreEmojisForCategory('recent');\r\n    }\r\n  }\r\n\r\n  /** Remove an emoji from the recent list and refresh the recent section */\r\n  removeFromRecent(emoji) {\r\n    this.recentEmojis = this.recentEmojis.filter(e => e !== emoji);\r\n    this.saveRecentEmojis();\r\n    if (this.categorySections['recent']) {\r\n      const recentList = this.categorySections['recent'].emojiList;\r\n      recentList.innerHTML = '';\r\n      this.loadedIndices['recent'] = 0;\r\n      this.loadMoreEmojisForCategory('recent');\r\n    }\r\n  }\r\n\r\n  /** Load recent emojis from localStorage */\r\n  loadRecentEmojis() {\r\n    try {\r\n      return JSON.parse(localStorage.getItem('recentEmojis')) || [];\r\n    } catch {\r\n      return [];\r\n    }\r\n  }\r\n\r\n  /** Save recent emojis to localStorage */\r\n  saveRecentEmojis() {\r\n    try {\r\n      localStorage.setItem('recentEmojis', JSON.stringify(this.recentEmojis));\r\n    } catch (e) {\r\n      console.error('Failed to save recent emojis:', e);\r\n    }\r\n  }\r\n\r\n  /** Highlight the active category in the navigation */\r\n  highlightCategory(categoryId) {\r\n    const buttons = this.categoryNav.querySelectorAll('.emoji-category-btn');\r\n    buttons.forEach(btn => {\r\n      btn.classList.toggle('active', btn.dataset.category === categoryId);\r\n    });\r\n  }\r\n\r\n  /** Show the emoji panel */\r\n  show() {\r\n    this.createPanel();\r\n    this.bindEvents();\r\n    this.loadAllEmojis();\r\n  }\r\n\r\n  /** Completely remove the emoji panel from DOM and clean up */\r\n  destroy() {\r\n    document.removeEventListener('keydown', this._emojiKeydownHandler);\r\n    document.removeEventListener('keydown', this._qKeydownHandler);\r\n    document.removeEventListener('click', this._documentClickHandler);\r\n    if (this.container) {\r\n      // Fade out the panel; the helper will remove it after transition.\r\n      (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.adjustVisibility)(this.container, 'hide', '0');\r\n    }\r\n    this.container = null;\r\n    this.searchInput = null;\r\n    this.emojiContainer = null;\r\n    this.categoryNav = null;\r\n    this.infoPanel = null;\r\n    this.infoIcon = null;\r\n    this.infoKeywords = null;\r\n    this.languageSelect = null;\r\n    EmojiPanel.instance = null;\r\n    if (this.options.emojiButton) {\r\n      this.options.emojiButton.title = 'Open emoji picker';\r\n    }\r\n    if (typeof this.options.onDestroy === 'function') {\r\n      this.options.onDestroy();\r\n    }\r\n  }\r\n\r\n  /** Toggle the visibility of the emoji panel */\r\n  toggle() {\r\n    if (document.querySelector('.emoji-panel')) {\r\n      this.destroy();\r\n    } else {\r\n      this.show();\r\n    }\r\n  }\r\n\r\n  /** Get the localized name for a category */\r\n  getLocalizedCategoryName(categoryKey) {\r\n    return this.categoryLabels[this.currentLanguage][categoryKey] || categoryKey;\r\n  }\r\n}\r\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/components/emojiPanel.js?");

/***/ }),

/***/ "./src/components/helpPanel.js":
/*!*************************************!*\
  !*** ./src/components/helpPanel.js ***!
  \*************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   HelpPanel: () => (/* binding */ HelpPanel)\n/* harmony export */ });\n/* harmony import */ var _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../helpers/helpers.js */ \"./src/helpers/helpers.js\");\n\r\n\r\nclass HelpPanel {\r\n  constructor(options = {}) {\r\n    this.container = null;\r\n    this.options = {\r\n      container: options.container || document.body,\r\n      helpButton: options.helpButton,\r\n      onDestroy: options.onDestroy\r\n    };\r\n    // Set the class instance to this\r\n    HelpPanel.instance = this;\r\n  }\r\n\r\n  init() {\r\n    this.createPanel();\r\n    this.bindEvents();\r\n    return this;\r\n  }\r\n\r\n  createPanel() {\r\n    this.container = document.createElement('div');\r\n    this.container.className = 'help-panel';\r\n    this.content = document.createElement('div');\r\n    this.content.className = 'help-content';\r\n    this.updatePanelContent();\r\n    this.container.appendChild(this.content);\r\n    document.body.appendChild(this.container);\r\n    // Fade in the panel\r\n    (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.adjustVisibility)(this.container, 'show', '1');\r\n  }\r\n\r\n  updatePanelContent() {\r\n    const lang = localStorage.getItem('emojiPanelLanguage') || 'en';\r\n    const helpTranslations = {\r\n      en: {\r\n        heading: \"Chat Commands & Hotkeys\",\r\n        sections: [\r\n          {\r\n            title: \"Chat Commands\",\r\n            items: [\r\n              { key: \"/help\", desc: \"Show this help panel\" },\r\n              { key: \"/me message\", desc: \"Send an action message\" },\r\n              { key: \"/pm username\", desc: \"Activate private chat mode with the specified user\" },\r\n              { key: \"/exit\", desc: \"Exit private chat mode\" },\r\n              { key: \"/reset\", desc: \"Reset the chat data\" },\r\n              { key: \"/colors\", desc: \"Show the username color panel\" },\r\n              { key: \"/import colors\", desc: \"Import user colors from a json file\" },\r\n              { key: \"/export colors\", desc: \"Export user colors to a json file\" }\r\n            ]\r\n          },\r\n          {\r\n            title: \"Chat Hotkeys\",\r\n            items: [\r\n              { key: \"Ctrl + Space\", desc: \"Hide/Show the chat\" },\r\n              { key: \"Shift + Ctrl + Space\", desc: \"Expand/Collapse the chat\" },\r\n              { key: \"Ctrl + Click\", desc: \"Activate private chat mode with the clicked user\" }\r\n            ]\r\n          },\r\n\r\n          {\r\n            heading: \"Emoji Panel Actions & Hotkeys\",\r\n            subSections: [\r\n              {\r\n                title: \"Emoji Panel Actions\",\r\n                items: [\r\n                  { key: \"Click an emoji\", desc: \"Insert the emoji\" },\r\n                  { key: \"Click outside panel\", desc: \"Closes the panel (emoji or help)\" }\r\n                ]\r\n              },\r\n              {\r\n                title: \"Emoji Panel Hotkeys\",\r\n                items: [\r\n                  { key: \"Ctrl + ;\", desc: \"Open the Emoji Panel\" },\r\n                  { key: \"Enter\", desc: \"Insert the emoji\" },\r\n                  { key: \"Ctrl + Enter\", desc: \"Insert the emoji keeping the panel open\" },\r\n                  { key: \"Ctrl + Click\", desc: \"Insert the emoji keeping the panel open\" },\r\n                  { key: \"Shift + Click\", desc: \"Remove emoji from recent list (in recent category)\" },\r\n                  { key: \"q\", desc: \"Hide the Emoji Panel (single press when search is not focused)\" },\r\n                  { key: \"qq\", desc: \"Hide the Emoji Panel (double press 'q' when search is focused)\" },\r\n                  { key: \"Esc\", desc: \"Close the panel (emoji or help)\" }\r\n                ]\r\n              }\r\n            ]\r\n          },\r\n\r\n          {\r\n            heading: \"Image Manipulations\",\r\n            subSections: [\r\n              {\r\n                title: \"Open/Close\",\r\n                items: [\r\n                  { key: \"(LMB) Click\", desc: \"Open the image\" },\r\n                  { key: \"Ctrl + (RMB)\", desc: \"Close the image and copy the link\" },\r\n                  { key: \"Space or ESC\", desc: \"Close the image\" }\r\n                ]\r\n              },\r\n              {\r\n                title: \"Movement and Scaling\",\r\n                items: [\r\n                  { key: \"Hold (MMB)\", desc: \"Drag the expanded image\" },\r\n                  { key: \"Scroll (MMB)\", desc: \"Zoom in/out the image\" },\r\n                  { key: \"Ctrl + (MMB)\", desc: \"Scale the image. Move the cursor up or down.\" }\r\n                ]\r\n              },\r\n              {\r\n                title: \"Navigation\",\r\n                items: [\r\n                  { key: \"Arrow keys (< >)\", desc: \"Switch between images\" },\r\n                  { key: \"(LMB), (RMB)\", desc: \"Switch between images\" }\r\n                ]\r\n              }\r\n            ]\r\n          },\r\n\r\n          {\r\n            heading: \"Markdown Formatting\",\r\n            items: [\r\n              { key: \"# Heading\", desc: \"Headings: use # for h1, ## for h2, up to ###### for h6\" },\r\n              { key: \"`code`\", desc: \"Inline code\" },\r\n              { key: \"**text**\", desc: \"Bold text\" },\r\n              { key: \"__text__\", desc: \"Italic text\" },\r\n              { key: \"~~text~~\", desc: \"Strikethrough text\" }\r\n            ]\r\n          },\r\n\r\n          {\r\n            heading: \"Delete / Show / Restore Messages\",\r\n            subSections: [\r\n              {\r\n                title: \"Deletion\",\r\n                items: [\r\n                  { key: \"(RMB) + Message\", desc: \"Delete message\" },\r\n                  { key: \"(RMB) + Nickname\", desc: \"Delete user's messages\" },\r\n                  { key: \"(RMB) + Time\", desc: \"Delete his messages from the selected time\" },\r\n                  { key: \"Ctrl + (RMB) + Time\", desc: \"Delete all messages from the selected time\" }\r\n                ]\r\n              },\r\n              {\r\n                title: \"Show / Restore\",\r\n                items: [\r\n                  { key: \"(LMB) + Toggle\", desc: \"Show/Hide messages\" },\r\n                  { key: \"Ctrl + (LMB) + Toggle\", desc: \"Restore hidden messages\" }\r\n                ]\r\n              }\r\n            ]\r\n          }\r\n\r\n        ]\r\n      },\r\n\r\n      ru: {\r\n        heading: \"Команды чата и горячие клавиши\",\r\n        sections: [\r\n          {\r\n            title: \"Команды чата\",\r\n            items: [\r\n              { key: \"/help\", desc: \"Показать панель помощи\" },\r\n              { key: \"/me сообщение\", desc: \"Отправить сообщение действия\" },\r\n              { key: \"/pm username\", desc: \"Активировать приватный чат для указанного пользователя\" },\r\n              { key: \"/exit\", desc: \"Выйти из приватного чата\" },\r\n              { key: \"/reset\", desc: \"Сбросить данные чата\" },\r\n              { key: \"/colors\", desc: \"Показать панель цветов имен пользователей\" },\r\n              { key: \"/import colors\", desc: \"Импортировать цвета пользователей из json файла\" },\r\n              { key: \"/export colors\", desc: \"Экспортировать цвета пользователей в json файл\" }\r\n            ]\r\n          },\r\n          {\r\n            title: \"Горячие клавиши чата\",\r\n            items: [\r\n              { key: \"Ctrl + Space\", desc: \"Скрыть/Показать чат\" },\r\n              { key: \"Shift + Ctrl + Space\", desc: \"Развернуть/Свернуть чат\" },\r\n              { key: \"Ctrl + Click\", desc: \"Активировать приватный чат для выбранного пользователя\" }\r\n            ]\r\n          },\r\n\r\n          {\r\n            heading: \"Действия и горячие клавиши панели эмодзи\",\r\n            subSections: [\r\n              {\r\n                title: \"Действия панели эмодзи\",\r\n                items: [\r\n                  { key: \"Click an emoji\", desc: \"Вставить эмодзи\" },\r\n                  { key: \"Click outside panel\", desc: \"Закрыть панель (эмодзи или помощь)\" }\r\n                ]\r\n              },\r\n              {\r\n                title: \"Горячие клавиши панели эмодзи\",\r\n                items: [\r\n                  { key: \"Ctrl + ;\", desc: \"Открыть панель эмодзи\" },\r\n                  { key: \"Enter\", desc: \"Вставить эмодзи\" },\r\n                  { key: \"Ctrl + Enter\", desc: \"Вставить эмодзи, оставив панель открытой\" },\r\n                  { key: \"Ctrl + Click\", desc: \"Вставить эмодзи, оставив панель открытой\" },\r\n                  { key: \"Shift + Click\", desc: \"Удалить эмодзи из списка \\\"Недавно использованные\\\"\" },\r\n                  { key: \"q\", desc: \"Скрыть панель эмодзи (одиночный нажим, когда поиск не в фокусе)\" },\r\n                  { key: \"qq\", desc: \"Скрыть панель эмодзи (дважды нажмите 'q', когда поиск в фокусе)\" },\r\n                  { key: \"Esc\", desc: \"Закрыть (эмодзи или помощь)\" }\r\n                ]\r\n              }\r\n            ]\r\n          },\r\n\r\n          {\r\n            heading: \"Манипуляции с изображением\",\r\n            subSections: [\r\n              {\r\n                title: \"Открытие/Закрытие\",\r\n                items: [\r\n                  { key: \"(ЛКМ) Клик\", desc: \"Открыть изображение\" },\r\n                  { key: \"Ctrl + (ПКМ)\", desc: \"Закрыть изображение и скопировать ссылку\" },\r\n                  { key: \"Space или ESC\", desc: \"Закрыть изображение\" }\r\n                ]\r\n              },\r\n              {\r\n                title: \"Перемещение и масштабирование\",\r\n                items: [\r\n                  { key: \"Зажатая (СКМ)\", desc: \"Перемещайте развернутое изображение\" },\r\n                  { key: \"Прокрутка (СКМ)\", desc: \"Увеличивайте/уменьшайте изображение\" },\r\n                  { key: \"Ctrl + (СКМ)\", desc: \"Масштабируйте изображение. Курсор вверх или вниз.\" }\r\n                ]\r\n              },\r\n              {\r\n                title: \"Навигация\",\r\n                items: [\r\n                  { key: \"Стрелки (< >)\", desc: \"Переключение между изображениями\" },\r\n                  { key: \"(ЛКМ), (ПКМ)\", desc: \"Переключение между изображениями\" }\r\n                ]\r\n              }\r\n            ]\r\n          },\r\n\r\n          {\r\n            heading: \"Форматирование Markdown\",\r\n            items: [\r\n              { key: \"# Заголовок\", desc: \"Заголовки: используйте # для h1, ## для h2, до ###### для h6\" },\r\n              { key: \"`код`\", desc: \"Встроенный код\" },\r\n              { key: \"**текст**\", desc: \"Жирный текст\" },\r\n              { key: \"__текст__\", desc: \"Курсивный текст\" },\r\n              { key: \"~~текст~~\", desc: \"Зачёркнутый текст\" }\r\n            ]\r\n          },\r\n\r\n          {\r\n            heading: \"Удаление / Показ / Восстановление сообщений\",\r\n            subSections: [\r\n              {\r\n                title: \"Удаление\",\r\n                items: [\r\n                  { key: \"(ПКМ) + Сообщение\", desc: \"Удалить сообщение\" },\r\n                  { key: \"(ПКМ) + Никнейм\", desc: \"Удалить сообщения пользователя\" },\r\n                  { key: \"(ПКМ) + Время\", desc: \"Удалить его сообщения с выбранного времени\" },\r\n                  { key: \"Ctrl + (ПКМ) + Время\", desc: \"Удалить все сообщения с выбранного времени\" }\r\n                ]\r\n              },\r\n              {\r\n                title: \"Показ / Восстановление\",\r\n                items: [\r\n                  { key: \"(ЛКМ) + Toggle\", desc: \"Показать/Скрыть сообщения\" },\r\n                  { key: \"Ctrl + (ЛКМ) + Toggle\", desc: \"Восстановить скрытые сообщения\" },\r\n                ]\r\n              }\r\n            ]\r\n          }\r\n\r\n        ]\r\n      }\r\n    };\r\n    const t = helpTranslations[lang];\r\n    let html = `<h5 class=\"help-section-header\">${t.heading}</h5>`;\r\n    t.sections.forEach(section => {\r\n      if (section.title) {\r\n        html += `<h6 class=\"help-section-subheader\">${section.title}</h6>`;\r\n      } else if (section.heading) {\r\n        html += `<h5 class=\"help-section-header\">${section.heading}</h5>`;\r\n      }\r\n      if (section.items) {\r\n        html += `<ul class=\"help-list\">`;\r\n        section.items.forEach(item => {\r\n          html += `<li class=\"help-list-item\"><strong class=\"help-hotkey\">${item.key}</strong> ${item.desc}</li>`;\r\n        });\r\n        html += `</ul>`;\r\n      }\r\n      if (section.subSections) {\r\n        section.subSections.forEach(sub => {\r\n          html += `<h6 class=\"help-section-subheader\">${sub.title}</h6>`;\r\n          html += `<ul class=\"help-list\">`;\r\n          sub.items.forEach(item => {\r\n            html += `<li class=\"help-list-item\"><strong class=\"help-hotkey\">${item.key}</strong> ${item.desc}</li>`;\r\n          });\r\n          html += `</ul>`;\r\n        });\r\n      }\r\n    });\r\n    this.content.innerHTML = html;\r\n  }\r\n\r\n  bindEvents() {\r\n    this._clickOutsideHandler = (e) => {\r\n      if (this.options.helpButton &&\r\n        (e.target === this.options.helpButton || this.options.helpButton.contains(e.target))) {\r\n        return;\r\n      }\r\n      if (this.container && !this.container.contains(e.target)) {\r\n        this.remove();\r\n        (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.showChatAlert)('Help panel has been closed.', {\r\n          type: 'warning',\r\n          duration: 2000\r\n        });\r\n      }\r\n    };\r\n    document.addEventListener('click', this._clickOutsideHandler, true);\r\n\r\n    this._escHandler = (e) => {\r\n      if (e.key === 'Escape') {\r\n        this.remove();\r\n        (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.showChatAlert)('Help panel has been closed.', {\r\n          type: 'warning',\r\n          duration: 2000\r\n        });\r\n      }\r\n    };\r\n    document.addEventListener('keydown', this._escHandler, true);\r\n\r\n    this._stopPropagationHandler = (e) => {\r\n      e.stopPropagation();\r\n    };\r\n    this.container.addEventListener('click', this._stopPropagationHandler);\r\n  }\r\n\r\n  remove() {\r\n    if (this._clickOutsideHandler) {\r\n      document.removeEventListener('click', this._clickOutsideHandler, true);\r\n      this._clickOutsideHandler = null;\r\n    }\r\n    if (this._escHandler) {\r\n      document.removeEventListener('keydown', this._escHandler, true);\r\n      this._escHandler = null;\r\n    }\r\n    if (this.container) {\r\n      this.container.removeEventListener('click', this._stopPropagationHandler);\r\n      this._stopPropagationHandler = null;\r\n    }\r\n    if (this.container) {\r\n      // Fade out the panel; the helper will remove the element after transition.\r\n      (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.adjustVisibility)(this.container, 'hide', '0');\r\n      this.container = null;\r\n    }\r\n    if (typeof this.options.onDestroy === 'function') {\r\n      this.options.onDestroy();\r\n    }\r\n    HelpPanel.instance = null;\r\n  }\r\n\r\n  show() {\r\n    if (!this.container) {\r\n      this.init();\r\n    } else {\r\n      this.updatePanelContent();\r\n    }\r\n    if (!document.body.contains(this.container)) {\r\n      document.body.appendChild(this.container);\r\n      (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.adjustVisibility)(this.container, 'show', '1');\r\n      (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.showChatAlert)(\"Help panel is now visible.\");\r\n    }\r\n  }\r\n\r\n  toggle() {\r\n    if (this.container && document.body.contains(this.container)) {\r\n      this.remove();\r\n    } else {\r\n      this.show();\r\n    }\r\n  }\r\n\r\n  static setupHelpCommandEvents() {\r\n    const input = document.getElementById('message-input');\r\n    if (input) {\r\n      input.addEventListener('keydown', (e) => {\r\n        if (input.value.trim() === \"/help\" && e.code === 'Space') {\r\n          e.preventDefault();\r\n          if (!HelpPanel.instance) {\r\n            const helpPanelInstance = new HelpPanel({\r\n              onDestroy: () => {\r\n                // Callback if needed.\r\n              }\r\n            });\r\n            helpPanelInstance.init();\r\n            helpPanelInstance.show();\r\n            (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.showChatAlert)(\"Help panel is now visible.\");\r\n          } else {\r\n            HelpPanel.instance.remove();\r\n          }\r\n          input.value = \"\";\r\n        }\r\n      });\r\n    }\r\n    return HelpPanel.instance;\r\n  }\r\n}\r\n\r\n// Static instance tracker\r\nHelpPanel.instance = null;\r\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/components/helpPanel.js?");

/***/ }),

/***/ "./src/components/updateCheck.js":
/*!***************************************!*\
  !*** ./src/components/updateCheck.js ***!
  \***************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   checkForUpdates: () => (/* binding */ checkForUpdates)\n/* harmony export */ });\nfunction checkForUpdates() {\r\n  const initialVersion = \"0.0.0\"; // Version used only for first run\r\n  const localVersionKey = \"KG_Chat_App_Version\";\r\n  const metaUrl = 'https://update.greasyfork.org/scripts/529368/KG_Chat_Application.meta.js';\r\n  const fullMetaUrl = metaUrl + '?rand=' + Date.now();\r\n  // Fallback download URL (user script URL) renamed to downloadUrl\r\n  const downloadUrl = 'https://update.greasyfork.org/scripts/529368/KG_Chat_Application.user.js';\r\n\r\n  // Initialize localStorage version to initialVersion if not set yet\r\n  if (!localStorage.getItem(localVersionKey)) {\r\n    localStorage.setItem(localVersionKey, initialVersion);\r\n  }\r\n\r\n  // Get the stored version (which is now guaranteed to exist)\r\n  const storedVersion = localStorage.getItem(localVersionKey);\r\n\r\n  fetch(fullMetaUrl)\r\n    .then(response => response.text())\r\n    .then(text => {\r\n      // Updated regex: allow an optional \"v\" or \"V\" prefix and trim extra spaces\r\n      const versionMatch = text.match(/@version\\s+v?([\\d.]+)/i);\r\n\r\n      if (!versionMatch) {\r\n        throw new Error(\"Version not found in meta file\");\r\n      }\r\n\r\n      // Trim to remove any accidental whitespace\r\n      const latestVersion = versionMatch[1].trim();\r\n\r\n      // Always use the fallback downloadUrl (named downloadUrl above)\r\n      // Compare the remote version with the stored version\r\n      if (compareVersions(latestVersion, storedVersion) > 0) {\r\n        showUpdatePopup(latestVersion, storedVersion, downloadUrl, () => {\r\n          localStorage.setItem(localVersionKey, latestVersion);\r\n        });\r\n      }\r\n    })\r\n    .catch(error => console.error('Update check failed:', error));\r\n}\r\n\r\nfunction showUpdatePopup(newVersion, currentVersion, downloadUrl, onUpdateComplete) {\r\n  // Create overlay and popup elements and assign classes for styling\r\n  const overlay = document.createElement('div');\r\n  overlay.className = 'update-overlay';\r\n\r\n  const popup = document.createElement('div');\r\n  popup.className = 'update-popup';\r\n\r\n  popup.innerHTML = `\r\n      <h2 class=\"update-header\">Update Available</h2>\r\n      <h2 class=\"update-script\">KG_Chat_Application</h2>\r\n      <p>A new version <span class=\"version\">${newVersion}</span> is available.</p>\r\n      <p>You are currently using version <span class=\"version\">${currentVersion}</span>.</p>\r\n      <div class=\"button-container\">\r\n        <button id=\"update-later\" class=\"update-later\">Later</button>\r\n        <button id=\"update-skip\" class=\"update-skip\">Skip</button>\r\n        <button id=\"update-now\" class=\"update-now\">Update Now</button>\r\n      </div>\r\n  `;\r\n\r\n  document.body.append(overlay, popup);\r\n\r\n  // \"Later\" button: simply dismiss the popup without updating stored version\r\n  document.getElementById('update-later').addEventListener('click', () => {\r\n    overlay.remove();\r\n    popup.remove();\r\n  });\r\n\r\n  // \"Skip\" button: dismiss the popup and update stored version so the user won't be prompted again for this version\r\n  document.getElementById('update-skip').addEventListener('click', () => {\r\n    if (onUpdateComplete) onUpdateComplete();\r\n    overlay.remove();\r\n    popup.remove();\r\n  });\r\n\r\n  // \"Update Now\" button: open a new tab with the download URL without updating the stored version\r\n  document.getElementById('update-now').addEventListener('click', () => {\r\n    if (onUpdateComplete) onUpdateComplete();\r\n    window.open(downloadUrl, '_blank');\r\n    overlay.remove();\r\n    popup.remove();\r\n  });\r\n}\r\n\r\n// Version compare helper function that supports versions with different lengths (e.g., 1.0 vs 1.0.1)\r\nfunction compareVersions(v1, v2) {\r\n  const parts1 = v1.split('.').map(Number);\r\n  const parts2 = v2.split('.').map(Number);\r\n  const maxLen = Math.max(parts1.length, parts2.length);\r\n  for (let i = 0; i < maxLen; i++) {\r\n    const num1 = parts1[i] || 0;\r\n    const num2 = parts2[i] || 0;\r\n    if (num1 > num2) return 1;\r\n    if (num1 < num2) return -1;\r\n  }\r\n  return 0;\r\n}\r\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/components/updateCheck.js?");

/***/ }),

/***/ "./src/converters/image-converter.js":
/*!*******************************************!*\
  !*** ./src/converters/image-converter.js ***!
  \*******************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   convertImageLinksToImage: () => (/* binding */ convertImageLinksToImage)\n/* harmony export */ });\n/* harmony import */ var _helpers_helpers__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../helpers/helpers */ \"./src/helpers/helpers.js\");\n // helpers\r\n\r\nlet bigImageEvents = {}; // Object to store event handlers\r\n\r\nfunction addBigImageEventListeners() {\r\n  Object.entries(bigImageEvents).forEach(([event, handler]) => {\r\n    document.addEventListener(event, handler);\r\n  });\r\n}\r\n\r\nfunction removeBigImageEventListeners() {\r\n  Object.entries(bigImageEvents).forEach(([event, handler]) => {\r\n    document.removeEventListener(event, handler);\r\n  });\r\n}\r\n\r\n// Image constants\r\nconst imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];\r\nconst emojis = { image: '📷', domain: '🖥️', untrusted: '💀️️' };\r\nconst zoomLimits = { min: 0.2, max: 10, factor: 0.1 };\r\nconst navigationDelay = 50;\r\n\r\n// Image navigation state\r\nlet currentIndex = 0;\r\nlet isChangingImage = false;\r\nlet thumbnailLinks = [];\r\n\r\n// Expanded image reference\r\nlet expandedImage = null;\r\n\r\nconst getExtension = (url) => {\r\n  try {\r\n    return (url.match(/\\.([^?#.]+)(?:[?#]|$)/i)?.[1]?.toLowerCase() || '');\r\n  } catch (error) {\r\n    console.error(\"Error extracting extension:\", error.message);\r\n    return '';\r\n  }\r\n};\r\n\r\nconst isAllowedImageExtension = (url) => {\r\n  const extension = getExtension(url);\r\n  return { allowed: imageExtensions.includes(extension), extension };\r\n};\r\n\r\nconst createExpandedView = (src, clickedThumbnailIndex) => {\r\n  // Create and add expanded image to DOM\r\n  const imageElement = document.createElement('img');\r\n  imageElement.src = src;\r\n  imageElement.classList.add('scaled-thumbnail');\r\n\r\n  document.body.appendChild(imageElement);\r\n\r\n  currentIndex = clickedThumbnailIndex;\r\n\r\n  // Zoom and movement variables\r\n  let zoomScale = 1;\r\n  let isMMBPressed = false;\r\n  let lastMouseX = 0,\r\n    lastMouseY = 0;\r\n  let translateX = 0,\r\n    translateY = 0;\r\n  const movementSpeed = 5;\r\n\r\n  // Get or create the dimming element\r\n  let dimmingElement = document.querySelector('.dimming-element');\r\n  if (!dimmingElement) {\r\n    dimmingElement = document.createElement('div');\r\n    dimmingElement.classList.add('dimming-element');\r\n    document.body.appendChild(dimmingElement);\r\n  }\r\n\r\n  // Add mobile touch support\r\n  if ((0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.checkIsMobile)()) {\r\n    // Variables to track touch state\r\n    let prevTouches = 0;\r\n    let prevDistance = 0;\r\n    let prevTouchX = 0;\r\n    let prevTouchY = 0;\r\n\r\n    const handleTouchStart = (event) => {\r\n      event.preventDefault();\r\n    };\r\n\r\n    const handleTouchMove = (event) => {\r\n      event.preventDefault(); // Prevent scrolling\r\n\r\n      const currentTouches = event.touches.length;\r\n\r\n      if (currentTouches === 2) {\r\n        // Pinch zoom with two fingers\r\n        const touch1 = event.touches[0];\r\n        const touch2 = event.touches[1];\r\n        const currentDistance = Math.hypot(\r\n          touch1.clientX - touch2.clientX,\r\n          touch1.clientY - touch2.clientY\r\n        );\r\n\r\n        if (prevTouches === 2) {\r\n          // Apply zoom only if previous event also had 2 touches\r\n          const zoomFactor = currentDistance / prevDistance;\r\n          zoomScale *= zoomFactor;\r\n          zoomScale = Math.max(zoomLimits.min, Math.min(zoomScale, zoomLimits.max));\r\n          imageElement.style.transform = `translate(-50%, -50%) translate(${translateX}px, ${translateY}px) scale(${zoomScale})`;\r\n        }\r\n        prevDistance = currentDistance;\r\n      } else if (currentTouches === 1) {\r\n        // Pan with one finger\r\n        const touch = event.touches[0];\r\n        const currentX = touch.clientX;\r\n        const currentY = touch.clientY;\r\n\r\n        if (prevTouches === 1) {\r\n          // Apply pan only if previous event also had 1 touch\r\n          const deltaX = currentX - prevTouchX;\r\n          const deltaY = currentY - prevTouchY;\r\n          translateX += deltaX;\r\n          translateY += deltaY;\r\n          imageElement.style.transform = `translate(-50%, -50%) translate(${translateX}px, ${translateY}px) scale(${zoomScale})`;\r\n        }\r\n        prevTouchX = currentX;\r\n        prevTouchY = currentY;\r\n      }\r\n\r\n      prevTouches = currentTouches; // Update previous touch count\r\n    };\r\n\r\n    const handleTouchEnd = (event) => {\r\n      if (event.touches.length === 0) {\r\n        prevTouches = 0; // Reset when all fingers are lifted\r\n      }\r\n    };\r\n\r\n    // Add touch event listeners with passive: false to allow preventDefault\r\n    imageElement.addEventListener('touchstart', handleTouchStart, { passive: false });\r\n    imageElement.addEventListener('touchmove', handleTouchMove, { passive: false });\r\n    imageElement.addEventListener('touchend', handleTouchEnd);\r\n  }\r\n\r\n  // Define closeExpandedView function\r\n  const closeExpandedView = (img) => {\r\n    (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.adjustVisibility)(img, 'hide', '0');\r\n    if (!document.querySelector('.popup-panel') && dimmingElement) {\r\n      (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.adjustVisibility)(dimmingElement, 'hide', '0');\r\n    }\r\n    removeBigImageEventListeners();\r\n  };\r\n\r\n  // Define event listeners for the expanded image (desktop)\r\n  bigImageEvents.click = (event) => {\r\n    if (!imageElement.contains(event.target)) {\r\n      imageElement.remove();\r\n      removeBigImageEventListeners();\r\n    }\r\n  };\r\n\r\n  bigImageEvents.keydown = (event) => {\r\n    if (event.code === 'Escape' || event.code === 'Space') {\r\n      event.preventDefault();\r\n      closeExpandedView(imageElement);\r\n    } else if (event.code === 'ArrowLeft') {\r\n      navigateImages(-1);\r\n    } else if (event.code === 'ArrowRight') {\r\n      navigateImages(1);\r\n    }\r\n  };\r\n\r\n  bigImageEvents.wheel = (event) => {\r\n    const direction = event.deltaY < 0 ? 1 : -1;\r\n    zoomScale += direction * zoomLimits.factor * zoomScale;\r\n    zoomScale = Math.max(zoomLimits.min, Math.min(zoomScale, zoomLimits.max));\r\n    imageElement.style.transform = `translate(-50%, -50%) translate(${translateX}px, ${translateY}px) scale(${zoomScale})`;\r\n  };\r\n\r\n  bigImageEvents.mousemove = (event) => {\r\n    if (isMMBPressed) {\r\n      if (event.ctrlKey) {\r\n        const deltaY = event.clientY - lastMouseY;\r\n        const zoomDirection = deltaY < 0 ? 1 : -1;\r\n        const zoomAmount = Math.abs(deltaY) * zoomLimits.factor * 0.05;\r\n        zoomScale += zoomDirection * zoomAmount * zoomScale;\r\n        zoomScale = Math.max(zoomLimits.min, Math.min(zoomScale, zoomLimits.max));\r\n      } else {\r\n        const deltaX = (event.clientX - lastMouseX) / zoomScale * movementSpeed;\r\n        const deltaY = (event.clientY - lastMouseY) / zoomScale * movementSpeed;\r\n        translateX += deltaX;\r\n        translateY += deltaY;\r\n      }\r\n      imageElement.style.transform = `translate(-50%, -50%) translate(${translateX}px, ${translateY}px) scale(${zoomScale})`;\r\n      lastMouseX = event.clientX;\r\n      lastMouseY = event.clientY;\r\n    }\r\n  };\r\n\r\n  bigImageEvents.mousedown = (event) => {\r\n    const { button, clientX, clientY, target, ctrlKey } = event;\r\n    if ((button === 0 || button === 2) && target !== imageElement) return;\r\n    if (button === 0) {\r\n      navigateImages(-1);\r\n    } else if (button === 2) {\r\n      event.preventDefault();\r\n      if (ctrlKey) {\r\n        navigator.clipboard.writeText(target.src).catch(console.error);\r\n        closeExpandedView(imageElement);\r\n      } else {\r\n        navigateImages(1);\r\n      }\r\n    } else if (button === 1) {\r\n      isMMBPressed = true;\r\n      lastMouseX = clientX;\r\n      lastMouseY = clientY;\r\n      event.preventDefault();\r\n    }\r\n  };\r\n\r\n  bigImageEvents.mouseup = (event) => {\r\n    if (event.button === 1) {\r\n      isMMBPressed = false;\r\n    }\r\n  };\r\n\r\n  bigImageEvents.contextmenu = (event) => event.preventDefault();\r\n\r\n  // Add the event listeners using your helper function\r\n  addBigImageEventListeners();\r\n\r\n  // Show the dimming element using adjustVisibility\r\n  (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.adjustVisibility)(dimmingElement, \"show\", \"1\");\r\n\r\n  // When clicking the dimming background, hide the image and the dimming element\r\n  dimmingElement.addEventListener('click', () => {\r\n    closeExpandedView(imageElement);\r\n  });\r\n\r\n  return imageElement;\r\n};\r\n\r\nconst navigateImages = (direction) => {\r\n  const newIndex = currentIndex + direction;\r\n  if (newIndex >= 0 && newIndex < thumbnailLinks.length && !isChangingImage) {\r\n    isChangingImage = true;\r\n    if (expandedImage) expandedImage.src = thumbnailLinks[newIndex].imgSrc;\r\n    setTimeout(() => (isChangingImage = false), navigationDelay);\r\n    currentIndex = newIndex;\r\n  }\r\n};\r\n\r\nfunction convertImageLinksToImage() {\r\n  const container = document.getElementById('messages-panel');\r\n  if (!container) return;\r\n\r\n  const refreshThumbnailLinks = () => {\r\n    thumbnailLinks = [];\r\n    container.querySelectorAll(\".clickable-thumbnail\").forEach((thumbnail, index) => {\r\n      const img = thumbnail.querySelector(\"img\");\r\n      if (img && thumbnail.dataset.sourceLink) {\r\n        thumbnailLinks.push({ link: thumbnail.dataset.sourceLink, imgSrc: img.src, index });\r\n      }\r\n    });\r\n  };\r\n\r\n  const links = container.querySelectorAll(\"a:not(.skipped):not(.processed-image)\");\r\n  if (!links.length) return;\r\n\r\n  links.forEach((link) => {\r\n    if (!link.href || !link.href.startsWith(\"http\")) return;\r\n    const { allowed, extension } = isAllowedImageExtension(link.href);\r\n    if (!allowed) return;\r\n\r\n    link.classList.add(\"media\");\r\n    const { isTrusted, domain } = (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.isTrustedDomain)(link.href);\r\n    link.title = (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.isEncodedURL)(link.href) ? (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.decodeURL)(link.href) : link.href;\r\n\r\n    isTrusted\r\n      ? handleTrustedLink(link, extension, domain)\r\n      : handleUntrustedLink(link, extension, domain);\r\n  });\r\n\r\n  function createThumbnail(link, isUntrusted) {\r\n    const thumbnail = document.createElement(\"div\");\r\n    thumbnail.classList.add(\"clickable-thumbnail\");\r\n    thumbnail.dataset.sourceLink = link.href;\r\n\r\n    const img = document.createElement(\"img\");\r\n    img.src = link.href;\r\n\r\n    img.onload = () => {\r\n      thumbnail.appendChild(img);\r\n      link.parentNode.insertBefore(thumbnail, link.nextSibling);\r\n      (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.scrollToBottom)(600);\r\n    };\r\n\r\n    img.onerror = () => {\r\n      console.error(\"Failed to load image:\", link.href);\r\n      link.classList.add(\"skipped\");\r\n    };\r\n\r\n    if (isUntrusted) {\r\n      if (!link.querySelector(\".clickable-thumbnail\")) {\r\n        link.addEventListener(\"click\", () => {\r\n          if (!link.querySelector(\".clickable-thumbnail\")) {\r\n            thumbnail.appendChild(img);\r\n            link.parentNode.insertBefore(thumbnail, link.nextSibling);\r\n          }\r\n        });\r\n      }\r\n    } else {\r\n      thumbnail.appendChild(img);\r\n      link.parentNode.insertBefore(thumbnail, link.nextSibling);\r\n    }\r\n\r\n    thumbnail.addEventListener(\"click\", (e) => {\r\n      e.stopPropagation();\r\n      refreshThumbnailLinks();\r\n      const clickedIndex = thumbnailLinks.findIndex(\r\n        (item) => item.link === link.href || item.imgSrc === img.src\r\n      );\r\n      expandedImage = createExpandedView(\r\n        img.src,\r\n        clickedIndex >= 0 ? clickedIndex : 0\r\n      );\r\n      (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.adjustVisibility)(expandedImage, \"show\", \"1\");\r\n      const dimmingElement = document.querySelector('.dimming-element');\r\n      if (dimmingElement) {\r\n        (0,_helpers_helpers__WEBPACK_IMPORTED_MODULE_0__.adjustVisibility)(dimmingElement, \"show\", \"1\");\r\n      }\r\n    });\r\n  }\r\n\r\n  function handleUntrustedLink(link, extension, domain) {\r\n    link.classList.add(\"skipped\");\r\n    link.textContent = `${emojis.image} ${extension.toUpperCase()} ${emojis.domain} ${domain} ${emojis.untrusted} Untrusted`;\r\n    link.addEventListener(\"click\", (e) => {\r\n      if (!link.classList.contains(\"processed-image\")) {\r\n        e.preventDefault();\r\n        link.classList.remove(\"skipped\");\r\n        link.classList.add(\"processed-image\");\r\n        createThumbnail(link, true);\r\n      }\r\n    });\r\n  }\r\n\r\n  function handleTrustedLink(link, extension, domain) {\r\n    link.textContent = `${emojis.image} ${extension.toUpperCase()} ${emojis.domain} ${domain}`;\r\n    link.classList.add(\"processed-image\");\r\n    createThumbnail(link, false);\r\n  }\r\n}\n\n//# sourceURL=webpack://tampermonkey-script/./src/converters/image-converter.js?");

/***/ }),

/***/ "./src/converters/video-converter.js":
/*!*******************************************!*\
  !*** ./src/converters/video-converter.js ***!
  \*******************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   convertVideoLinksToPlayer: () => (/* binding */ convertVideoLinksToPlayer)\n/* harmony export */ });\n/* harmony import */ var _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../helpers/helpers.js */ \"./src/helpers/helpers.js\");\n\r\n\r\n// Constants\r\nconst emojis = {\r\n  channel: '📺',\r\n  title: '📹',\r\n  type: '🎬️',\r\n  domain: '🖥️',\r\n  untrusted: '💀️️'\r\n};\r\nconst allowedVideoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi'];\r\n\r\n// Utility Functions\r\n\r\n/** Checks if a URL has an allowed video extension */\r\nconst isAllowedVideoExtension = url => {\r\n  const ext = url.match(/\\.([^?#.]+)(?:[?#]|$)/i)?.[1]?.toLowerCase() || '';\r\n  return { allowed: allowedVideoExtensions.includes(ext), extension: ext };\r\n};\r\n\r\n// Global Variables\r\nlet sharedYouTubePlayer = null; // Shared YouTube player instance\r\nlet activeYouTubePlaceholder = null; // Tracks the currently active YouTube preview\r\n\r\n/** Returns or creates a shared YouTube iframe player */\r\nfunction getSharedYouTubePlayer() {\r\n  if (!sharedYouTubePlayer) {\r\n    sharedYouTubePlayer = document.createElement('iframe');\r\n    sharedYouTubePlayer.classList.add(\"video-container\");\r\n    sharedYouTubePlayer.allowFullscreen = true;\r\n  }\r\n  return sharedYouTubePlayer;\r\n}\r\n\r\n/** Fetches YouTube metadata using the oEmbed endpoint */\r\nasync function fetchYouTubeMetadata(videoId) {\r\n  const oembedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}&format=json`;\r\n  try {\r\n    const response = await fetch(oembedUrl);\r\n    const data = await response.json();\r\n    const title = data.title || 'Title not found';\r\n    const channel = data.author_name || 'Channel not found';\r\n    return { title, channel };\r\n  } catch (error) {\r\n    console.error('Error fetching YouTube metadata:', error);\r\n    return { title: 'Error', channel: 'Error' };\r\n  }\r\n}\r\n\r\n/** Renders a YouTube preview with metadata and thumbnail */\r\nasync function renderYouTubePreview(infoContainer, placeholder, videoId, videoType) {\r\n  // Clear both containers\r\n  infoContainer.innerHTML = \"\";\r\n  placeholder.innerHTML = \"\";\r\n\r\n  const metadata = await fetchYouTubeMetadata(videoId);\r\n\r\n  const channel = document.createElement('span');\r\n  channel.classList.add(\"channel-name\");\r\n  channel.textContent = `${emojis.channel} ${metadata.channel}`;\r\n\r\n  const title = document.createElement('span');\r\n  title.classList.add(\"video-title\");\r\n  title.textContent = `${emojis.title} ${metadata.title}`;\r\n\r\n  infoContainer.append(channel, title);\r\n\r\n  const thumb = document.createElement('img');\r\n  thumb.src = `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;\r\n  thumb.alt = videoType;\r\n  thumb.classList.add(\"youtube-thumb\");\r\n  placeholder.appendChild(thumb);\r\n\r\n  // Wait for the thumbnail to load before scrolling\r\n  thumb.addEventListener('load', () => {\r\n    (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.scrollToBottom)(600);\r\n  });\r\n}\r\n\r\n/** Extracts video information from a URL */\r\nfunction getVideoInfo(url) {\r\n  const youtubeMatch = url.match(/(?:shorts\\/|live\\/|watch\\?v=|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/i);\r\n  if (youtubeMatch) {\r\n    const videoId = youtubeMatch[1];\r\n    const videoType = url.includes('shorts/') ? 'Shorts' :\r\n      url.includes('live/') ? 'Live' :\r\n        url.includes('watch?v=') ? 'Watch' :\r\n          url.includes('youtu.be/') ? 'Share' : 'YouTube';\r\n    return { youtubeMatch: true, videoId, videoType };\r\n  }\r\n\r\n  const extension = url.split('.').pop().toLowerCase();\r\n  if (allowedVideoExtensions.includes(extension)) {\r\n    return { youtubeMatch: false, videoType: `Video (${extension.toUpperCase()})` };\r\n  }\r\n  return false;\r\n}\r\n\r\n/** Main function to convert video links to players or previews */\r\nfunction convertVideoLinksToPlayer() {\r\n  const container = document.getElementById('messages-panel');\r\n  if (!container) return;\r\n\r\n  const links = container.querySelectorAll(\"a:not(.skipped):not(.processed-video)\");\r\n  if (!links.length) return;\r\n\r\n  links.forEach(link => {\r\n    const url = link.href;\r\n    if (!url) return;\r\n\r\n    const videoInfo = getVideoInfo(url);\r\n    if (!videoInfo) return;\r\n\r\n    link.classList.add(\"media\");\r\n    const { isTrusted, domain } = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.isTrustedDomain)(url);\r\n\r\n    if (!isTrusted) {\r\n      link.classList.add(\"skipped\");\r\n      link.textContent = `${emojis.type} ${videoInfo.videoType} ${emojis.domain} ${domain} ${emojis.untrusted} Untrusted`;\r\n      link.addEventListener(\"click\", e => {\r\n        if (!link.classList.contains(\"processed-video\")) {\r\n          e.preventDefault();\r\n          link.classList.remove(\"skipped\");\r\n          processVideoLink(link, url, domain, videoInfo);\r\n        }\r\n      });\r\n      return;\r\n    }\r\n\r\n    processVideoLink(link, url, domain, videoInfo);\r\n  });\r\n\r\n  /** Processes a single video link */\r\n  async function processVideoLink(link, url, domain, videoInfo) {\r\n    const { youtubeMatch, videoType, videoId } = videoInfo;\r\n    const videoCheck = isAllowedVideoExtension(url);\r\n    if (!youtubeMatch && !videoCheck.allowed) return;\r\n\r\n    link.classList.add(\"processed-video\");\r\n\r\n    const wrapper = document.createElement('div');\r\n    wrapper.classList.add(\"video-wrapper\");\r\n\r\n    link.textContent = `${emojis.type} ${videoType} ${emojis.domain} ${domain}`;\r\n    link.title = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.isEncodedURL)(url) ? (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.decodeURL)(url) : url;\r\n    link.style.display = 'inline-flex';\r\n\r\n    if (youtubeMatch) {\r\n      // Create the info container first\r\n      const infoContainer = document.createElement('div');\r\n      infoContainer.classList.add(\"youtube-info\");\r\n\r\n      // Create placeholder\r\n      const placeholder = document.createElement('div');\r\n      placeholder.classList.add(\"youtube-placeholder\");\r\n      placeholder.dataset.videoId = videoId;\r\n      placeholder.dataset.videoType = videoType;\r\n\r\n      // Add elements to the DOM first\r\n      link.parentNode.insertBefore(wrapper, link);\r\n      wrapper.append(link, infoContainer, placeholder);\r\n\r\n      // Then render the YouTube preview\r\n      await renderYouTubePreview(infoContainer, placeholder, videoId, videoType);\r\n\r\n      placeholder.addEventListener(\"click\", () => {\r\n        if (activeYouTubePlaceholder && activeYouTubePlaceholder !== placeholder) {\r\n          const prevVideoId = activeYouTubePlaceholder.dataset.videoId;\r\n          const prevVideoType = activeYouTubePlaceholder.dataset.videoType;\r\n          renderYouTubePreview(\r\n            activeYouTubePlaceholder.previousElementSibling,\r\n            activeYouTubePlaceholder,\r\n            prevVideoId,\r\n            prevVideoType\r\n          );\r\n        }\r\n        activeYouTubePlaceholder = placeholder;\r\n\r\n        const player = getSharedYouTubePlayer();\r\n        player.src = `https://www.youtube.com/embed/${videoId}?autoplay=1`;\r\n        placeholder.innerHTML = \"\";\r\n        placeholder.appendChild(player);\r\n      });\r\n    } else {\r\n      const embed = document.createElement('video');\r\n      embed.classList.add(\"video-container\");\r\n      embed.src = url;\r\n      embed.controls = true;\r\n\r\n      link.parentNode.insertBefore(wrapper, link);\r\n      wrapper.append(link, embed);\r\n    }\r\n  }\r\n\r\n}\n\n//# sourceURL=webpack://tampermonkey-script/./src/converters/video-converter.js?");

/***/ }),

/***/ "./src/data/animations.js":
/*!********************************!*\
  !*** ./src/data/animations.js ***!
  \********************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   addJumpEffect: () => (/* binding */ addJumpEffect),\n/* harmony export */   addPulseEffect: () => (/* binding */ addPulseEffect),\n/* harmony export */   addShakeEffect: () => (/* binding */ addShakeEffect)\n/* harmony export */ });\nfunction addJumpEffect(element, initialTranslateX = 0, initialTranslateY = 0) {\r\n  // Define keyframes with specified percentages, scale effect, and calc for Y translation\r\n  const keyframes = [\r\n    { transform: `translate(${initialTranslateX}%, calc(${initialTranslateY}%)) scale(1)` }, // 0%\r\n    { transform: `translate(${initialTranslateX}%, calc(${initialTranslateY}% - 60%)) scale(1.1)` }, // 20%\r\n    { transform: `translate(${initialTranslateX}%, calc(${initialTranslateY}% + 15%)) scale(1)` }, // 40%\r\n    { transform: `translate(${initialTranslateX}%, calc(${initialTranslateY}% - 20%)) scale(1.05)` }, // 60%\r\n    { transform: `translate(${initialTranslateX}%, calc(${initialTranslateY}% + 8%)) scale(1)` }, // 75%\r\n    { transform: `translate(${initialTranslateX}%, calc(${initialTranslateY}% - 10%)) scale(1.05)` }, // 85%\r\n    { transform: `translate(${initialTranslateX}%, calc(${initialTranslateY}% + 4%)) scale(1)` }, // 92%\r\n    { transform: `translate(${initialTranslateX}%, calc(${initialTranslateY}%)) scale(1)` } // 100%\r\n  ];\r\n\r\n  // Animation options\r\n  const options = {\r\n    duration: 500, // Total animation duration in ms (adjust as needed)\r\n    easing: 'ease', // Smooth easing between keyframes\r\n    iterations: 1 // Play once\r\n  };\r\n\r\n  // Start the animation\r\n  const animation = element.animate(keyframes, options);\r\n\r\n  // Optional: Return a promise that resolves when animation completes\r\n  return animation.finished;\r\n}\r\n\r\n// Helper function to add shake effect\r\nfunction addShakeEffect(element) {\r\n  element.classList.add('shake-effect');\r\n  setTimeout(() => {\r\n    element.classList.remove('shake-effect');\r\n  }, 500);\r\n}\r\n\r\nfunction addPulseEffect(element) {\r\n  element.classList.add('pulse-effect');\r\n  setTimeout(() => {\r\n    element.classList.remove('pulse-effect');\r\n  }, 500);\r\n}\n\n//# sourceURL=webpack://tampermonkey-script/./src/data/animations.js?");

/***/ }),

/***/ "./src/data/definitions.js":
/*!*********************************!*\
  !*** ./src/data/definitions.js ***!
  \*********************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   BASE_URL: () => (/* binding */ BASE_URL),\n/* harmony export */   GAME_URL: () => (/* binding */ GAME_URL),\n/* harmony export */   XMPP_BIND_URL: () => (/* binding */ XMPP_BIND_URL),\n/* harmony export */   connectionDelay: () => (/* binding */ connectionDelay),\n/* harmony export */   emojiFaces: () => (/* binding */ emojiFaces),\n/* harmony export */   reconnectionDelay: () => (/* binding */ reconnectionDelay),\n/* harmony export */   trustedDomains: () => (/* binding */ trustedDomains),\n/* harmony export */   userListDelay: () => (/* binding */ userListDelay)\n/* harmony export */ });\n/* harmony import */ var _styles_style_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../styles/style.scss */ \"./src/styles/style.scss\");\n/* harmony import */ var _styles_emojiPanel_scss__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../styles/emojiPanel.scss */ \"./src/styles/emojiPanel.scss\");\n/* harmony import */ var _styles_helpPanel_scss__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../styles/helpPanel.scss */ \"./src/styles/helpPanel.scss\");\n/* harmony import */ var _styles_updateCheck_scss__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../styles/updateCheck.scss */ \"./src/styles/updateCheck.scss\");\n/* harmony import */ var _styles_chatUsernameColors_scss__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../styles/chatUsernameColors.scss */ \"./src/styles/chatUsernameColors.scss\");\n // main styles\r\n // emoji panel styles\r\n // help panel styles\r\n // update panel styles\r\n // chat username colors styles\r\n\r\n// URL constants\r\nconst BASE_URL = 'https://klavogonki.ru';\r\nconst GAME_URL = `${BASE_URL}/g/?gmid=`;\r\nconst XMPP_BIND_URL = `${BASE_URL}/xmpp-httpbind/`;\r\n\r\n// Sleep time (ms)\r\nconst connectionDelay = 100;\r\nconst userListDelay = 5000;\r\nconst reconnectionDelay = 5000;\r\n\r\nconst emojiFaces = [\r\n  // People Emojis (Facial expressions)\r\n  '😀', '😁', '😂', '🤣', '😃', '😄', '😅', '😆',\r\n  '😉', '😊', '😋', '😎', '😏', '😐', '😑', '😒',\r\n  '😓', '😔', '😕', '😖', '😗', '😘', '😙', '😚',\r\n  '😜', '😝', '😛', '🤑', '🤗', '🤔', '🤐', '🤨',\r\n  '😣', '😥', '😮', '🤯', '😳', '😱', '😨', '😰',\r\n  '😢', '🤪', '😵', '😲', '🤤', '😷', '🤒', '🤕',\r\n  '🤢', '🤧', '😇', '🥳', '🥺', '😬', '😴', '😌',\r\n  '🤥', '🥴', '🥵', '🥶', '🤧', '🤭', '🤫', '😠',\r\n  '😡', '😳', '😞', '😟', '😕',\r\n\r\n  // Cat Emojis (Expressive faces of cats)\r\n  '🐱', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾',\r\n\r\n  // Other Animal Emojis (Various animals' faces)\r\n  '🐶', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼',\r\n  '🐨', '🐯', '🦁', '🐮', '🐷', '🐸', '🐵',\r\n  '🙈', '🙉', '🙊', '🐔', '🦄'\r\n];\r\n\r\n// List of trusted domains\r\nconst trustedDomains = [\r\n  'klavogonki.ru',\r\n  'youtube.com', // youtube main\r\n  'youtu.be', // youtube share\r\n  'imgur.com',\r\n  'pikabu.ru',\r\n  'userapi.com', // vk.com\r\n  'ibb.co', // imgbb.com\r\n  'yaplakal.com',\r\n  'freepik.com'\r\n];\n\n//# sourceURL=webpack://tampermonkey-script/./src/data/definitions.js?");

/***/ }),

/***/ "./src/data/emojiData.js":
/*!*******************************!*\
  !*** ./src/data/emojiData.js ***!
  \*******************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   emojiData: () => (/* binding */ emojiData),\n/* harmony export */   emojiKeywords: () => (/* binding */ emojiKeywords)\n/* harmony export */ });\nconst emojiData = {\r\n  smileys: [\r\n    // Face smiling\r\n    '😀', '😃', '😄', '😆', '😁', '😅', '😂', '🤣', '🥲', '☺️', '😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰', '😙', '😚', '😗', '😘',\r\n    '😋', '🥸', '😵‍💫',\r\n    // Face affection\r\n    '😛', '😝', '😜', '🤪', '😎', '🤓', '🧐', '🤨', '🤩', '🥳', '😏', '😒', '😞', '😔', '😟', '😕', '🙁', '☹️', '😣', '😖', '😫',\r\n    // Face tongue\r\n    '😩', '🥺', '😢', '😭', '😤', '😠', '😡', '🤬', '🤯', '😳', '🥵', '🥶', '😱', '😨', '😰', '😥', '😓', '🤗', '🤔', '🤭', '🤫',\r\n    // Face negative\r\n    '🤥', '😶', '😐', '😑', '😬', '🙄', '😯', '😦', '😧', '😮', '😲', '🥱', '😴', '🤤', '😪', '😵', '🤐', '🥴', '🤢', '🤮', '🤧',\r\n    // Face costume\r\n    '😷', '🤒', '🤕', '🤑', '🤠', '😈', '👿', '👹', '👺', '🤡', '💩', '👻', '💀', '☠️', '👽', '👾', '🤖', '🎃',\r\n    // Cat faces\r\n    '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾',\r\n    // Hand gestures\r\n    '👋', '🤚', '🖐️', '✋', '🖖', '👌', '🤌', '🤏', '✌️', '🤞', '🤟', '🤘', '🤙', '👈', '👉', '👆', '🖕', '👇', '☝️', '👍', '👎',\r\n    // Hand symbols\r\n    '✊', '👊', '🤛', '🤜', '👏', '🙌', '👐', '🤲', '🤝', '🙏',\r\n    // Body parts\r\n    '✍️', '💅', '🤳', '💪', '🦾', '🦿', '🦵', '🦶', '👂', '🦻', '👃', '🧠', '🫀', '🫁', '🦷', '🦴', '👀', '👁️', '👅', '👄',\r\n    // Person\r\n    '👶', '🧒', '👦', '👧', '🧑', '👱', '👨', '🧔', '👩', '🧓', '👴', '👵', '🙍', '🙎', '🙅', '🙆', '💁', '🙋', '🧏', '🙇', '🤦', '🤷',\r\n    // Professional\r\n    '👮', '🕵️', '💂', '🥷', '👷', '🤴', '👸', '👳', '👲', '🧕', '🤵', '👰', '🤰', '🤱', '👼', '🎅', '🤶', '🦸', '🦹', '🧙', '🧚', '🧛', '🧜'\r\n  ],\r\n\r\n  nature: [\r\n    // Mammals\r\n    '🐵', '🐒', '🦍', '🦧', '🐶', '🐕', '🦮', '🐕‍🦺', '🐩', '🐺', '🦊', '🦝', '🐱', '🐈', '🐈‍⬛', '🦁', '🐯', '🐅', '🐆', '🐴', '🐎',\r\n    '🦄', '🦓', '🦌', '🦬', '🐮', '🐂', '🐃', '🐄', '🐷', '🐖', '🐗', '🐽', '🐏', '🐑', '🐐', '🐪', '🐫', '🦙', '🦒', '🐘', '🦏',\r\n    '🦛', '🐭', '🐁', '🐀', '🐹', '🐰', '🐇', '🦫', '🦘', '🦡', '🐿️', '🦔', '🦦', '🦥', '🐼', '🦨', '🦘', '🦡',\r\n    // Birds\r\n    '🦃', '🐔', '🐓', '🐣', '🐤', '🐥', '🐦', '🐧', '🕊️', '🦅', '🦆', '🦢', '🦉', '🦤', '🪶', '🦩', '🦚', '🦜',\r\n    // Reptiles/Amphibians\r\n    '🐸', '🐊', '🐢', '🦎', '🐍', '🐲', '🐉', '🦕', '🦖',\r\n    // Marine\r\n    '🐳', '🐋', '🐬', '🦭', '🐟', '🐠', '🐡', '🦈', '🐙', '🐚', '🪸',\r\n    // Insects\r\n    '🐌', '🦋', '🐛', '🐜', '🐝', '🪲', '🐞', '🦗', '🪳', '🕷️', '🕸️', '🦂', '🦟', '🪰', '🪱',\r\n    // Plants\r\n    '🌸', '💮', '🏵️', '🌹', '🥀', '🌺', '🌻', '🌼', '🌷', '🌱', '🪴', '🌲', '🌳', '🌴', '🌵', '🌾', '🌿', '☘️', '🍀', '🍁', '🍂', '🍃'\r\n  ],\r\n\r\n  food: [\r\n    // Fruits\r\n    '🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🫐', '🍈', '🍒', '🍑', '🥭', '🍍', '🥥', '🥝',\r\n    // Vegetables\r\n    '🍅', '🍆', '🥑', '🥦', '🥬', '🥒', '🌶️', '🫑', '🥕', '🧄', '🧅', '🥔', '🍠', '🥐', '🥯', '🍞', '🥖', '🥨',\r\n    // Prepared\r\n    '🧀', '🥚', '🍳', '🥓', '🥩', '🍗', '🍖', '🦴', '🌭', '🍔', '🍟', '🍕', '🫓', '🥪', '🥙', '🧆', '🌮', '🌯', '🫔', '🥗',\r\n    // Asian\r\n    '🥘', '🫕', '🥫', '🍝', '🍜', '🍲', '🍛', '🍣', '🍱', '🥟', '🦪', '🍤', '🍙', '🍚', '🍘', '🍥', '🥠', '🥮',\r\n    // Sweets\r\n    '🍢', '🍡', '🍧', '🍨', '🍦', '🥧', '🧁', '🍰', '🎂', '🍮', '🍭', '🍬', '🍫', '🍿', '🍩', '🍪',\r\n    // Drink\r\n    '🫖', '☕', '🍵', '🧃', '🥤', '🧋', '🍶', '🍺', '🍻', '🥂', '🍷', '🥃', '🍸', '🍹', '🧉', '🍾'\r\n  ],\r\n\r\n  activities: [\r\n    // Sports\r\n    '⚽', '🏀', '🏈', '⚾', '🥎', '🎾', '🏐', '🏉', '🥏', '🎱', '🪀', '🏓', '🏸', '🏒', '🏑', '🥍', '🏏', '⛳', '🪁', '🎣',\r\n    '🤿', '🎽', '🛹', '🛼', '🛷', '⛸️', '🥌', '⛷️', '🏂', '🪂', '🏋️', '🤼', '🤸', '⛹️', '🤾', '🏌️', '🏇', '🧘', '🏄', '🏊',\r\n    // Activities\r\n    '🤽', '🚣', '🧗', '🚴', '🚵', '🎪', '🎭', '🎨', '🎬', '🎤', '🎧', '🎼', '🎹', '🥁', '🎷', '🎺', '🎸', '🎻', '🎲', '🎯',\r\n    '🎳', '🎮', '🎰', '🧩', '🎪', '🎫', '🎟️'\r\n  ],\r\n\r\n  travel: [\r\n    // Land transport\r\n    '🚗', '🚕', '🚙', '🚌', '🚎', '🏎️', '🚓', '🚑', '🚒', '🚐', '🛻', '🚚', '🚛', '🚜', '🛵', '🏍️', '🛺', '🚲', '🛴',\r\n    // Air transport\r\n    '✈️', '🛩️', '🛫', '🛬', '🚁', '🚀', '🛸',\r\n    // Water transport\r\n    '🛶', '⛵', '🚤', '🛥️', '🛳️', '⛴️', '🚢',\r\n    // Places\r\n    '🏰', '🏯', '🏟️', '🏖️', '🏝️', '🏜️', '🌋', '⛰️', '🏔️', '🗻', '🏕️', '🏭', '🏢', '🏬', '🏣', '🏤', '🏥', '🏦', '🏨',\r\n    '🏪', '🏫', '🏩', '💒', '⛪', '🕌', '🕍', '🛕', '⛩️', '🏛️'\r\n  ],\r\n\r\n  objects: [\r\n    // Tools\r\n    '📱', '💻', '⌨️', '🖥️', '🖨️', '🖱️', '🖲️', '🕹️', '🗜️', '💽', '💾', '💿', '📀', '📼', '📷', '📸', '📹', '🎥',\r\n    // Office\r\n    '📞', '☎️', '📟', '📠', '📺', '📻', '🎙️', '🎚️', '🎛️', '📡', '🔋', '🔌', '💡', '🔦', '🕯️',\r\n    // Household\r\n    '🧯', '🛢️', '💸', '💵', '💴', '💶', '💷', '🪙', '💰', '💳', '💎', '⚖️', '🪜', '🧰', '🔧', '🔨', '⚒️', '🛠️', '⛏️',\r\n    // Writing\r\n    '✏️', '🖊️', '🖋️', '✒️', '🖌️', '🖍️', '📝', '📚', '📖', '🔖', '📑', '🗒️', '📄', '📰', '🗞️', '📁', '📂', '🗂️',\r\n    // Clocks (additional clock emojis)\r\n    '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚', '🕛', '🕰️', '⏰', '⏱️', '⏲️', '⌚'\r\n  ],\r\n\r\n  symbols: [\r\n    // Hearts\r\n    '❤️', '🧡', '💛', '💚', '💙', '💜', '🤎', '🖤', '🤍', '💔', '❣️', '💕', '💞', '💓', '💗', '💖', '💘', '💝', '💟',\r\n    // Religion\r\n    '☮️', '✝️', '☪️', '🕉️', '☸️', '✡️', '🔯', '🕎', '☯️', '☦️', '🛐', '⛎',\r\n    // Warning\r\n    '⚠️', '🚸', '⛔', '🚫', '☢️', '☣️',\r\n    // Math\r\n    '➕', '➖', '➗', '✖️', '♾️', '💲', '💱',\r\n    // Arrows\r\n    '⬆️', '↗️', '➡️', '↘️', '⬇️', '↙️', '⬅️', '↖️', '↕️', '↔️', '↩️', '↪️', '⤴️', '⤵️', '🔃', '🔄',\r\n    // Other\r\n    '🔆', '📶', '🎦', '🔅', '♻️', '✅', '❌', '❎', '➰', '➿', '〽️', '✳️', '✴️', '❇️', '©️', '®️', '™️'\r\n  ],\r\n\r\n  flags: [\r\n    // Special flags\r\n    '🏁', '🚩', '🎌', '🏴', '🏳️', '🏳️‍🌈', '🏳️‍⚧️', '🏴‍☠️',\r\n    // Country flags (sample)\r\n    '🇺🇸', '🇬🇧', '🇯🇵', '🇰🇷', '🇩🇪', '🇨🇳', '🇧🇷', '🇮🇳', '🇫🇷', '🇪🇸', '🇮🇹', '🇷🇺', '🇨🇦', '🇦🇺', '🇳🇿',\r\n    // More popular country flags\r\n    '🇲🇽', '🇦🇷', '🇵🇰', '🇪🇬', '🇸🇪', '🇳🇴', '🇳🇱', '🇨🇭', '🇹🇷', '🇮🇩', '🇸🇬', '🇮🇱', '🇵🇹', '🇵🇱', '🇹🇭'\r\n  ]\r\n};\r\n\r\nconst emojiKeywords = {\r\n  //------------------------- Smileys & Emotion -------------------------\r\n  // Face smiling\r\n  '😀': {\r\n    en: ['grinning', 'face', 'smile', 'happy', 'joy'],\r\n    ru: ['улыбающийся', 'лицо', 'улыбка', 'счастливый', 'радость']\r\n  },\r\n  '😃': {\r\n    en: ['smiley', 'face', 'happy', 'joy', 'laugh'],\r\n    ru: ['улыбчивый', 'лицо', 'счастливый', 'радость', 'смех']\r\n  },\r\n  '😄': {\r\n    en: ['laughing', 'face', 'happy', 'joy', 'grin'],\r\n    ru: ['смеющийся', 'лицо', 'счастливый', 'радость', 'ухмылка']\r\n  },\r\n  '😆': {\r\n    en: ['grinning face', 'laugh'],\r\n    ru: ['улыбка', 'смех']\r\n  },\r\n  '😁': {\r\n    en: ['beaming', 'face', 'grin', 'smile', 'happy'],\r\n    ru: ['сияющий', 'лицо', 'улыбка', 'счастье', 'радость']\r\n  },\r\n  '😅': {\r\n    en: ['sweat', 'nervous', 'face', 'laugh', 'relief'],\r\n    ru: ['пот', 'нервный', 'лицо', 'смех', 'облегчение']\r\n  },\r\n  '😂': {\r\n    en: ['tears', 'joy', 'face', 'laugh', 'happy'],\r\n    ru: ['слёзы', 'радость', 'лицо', 'смех', 'счастье']\r\n  },\r\n  '🤣': {\r\n    en: ['rolling', 'floor', 'laugh', 'funny', 'amused'],\r\n    ru: ['катающийся', 'пол', 'смех', 'смешной', 'развлечённый']\r\n  },\r\n  '🥲': {\r\n    en: ['smiling', 'tear', 'bittersweet', 'nostalgic', 'happy'],\r\n    ru: ['улыбающийся', 'слеза', 'горько-сладкий', 'ностальгический', 'счастливый']\r\n  },\r\n  '☺️': {\r\n    en: ['smile', 'blush', 'content', 'peaceful'],\r\n    ru: ['улыбка', 'румянец', 'довольный', 'спокойный']\r\n  },\r\n  '😊': {\r\n    en: ['smiling', 'happy', 'blushing', 'content'],\r\n    ru: ['улыбающийся', 'счастливый', 'румянец', 'довольный']\r\n  },\r\n  '😇': {\r\n    en: ['angel', 'halo', 'innocent', 'saint', 'pure'],\r\n    ru: ['ангел', 'ореол', 'невинный', 'святой', 'чистый']\r\n  },\r\n  '🙂': {\r\n    en: ['slight', 'smile', 'face', 'mild'],\r\n    ru: ['слегка', 'улыбка', 'лицо', 'умеренный']\r\n  },\r\n  '🙃': {\r\n    en: ['upside-down', 'silly', 'quirky', 'funny'],\r\n    ru: ['перевернутый', 'глупый', 'странный', 'смешной']\r\n  },\r\n  '😉': {\r\n    en: ['wink', 'flirt', 'playful', 'smile'],\r\n    ru: ['подмигивание', 'флирт', 'игривый', 'улыбка']\r\n  },\r\n  '😌': {\r\n    en: ['relieved', 'calm', 'content', 'satisfied'],\r\n    ru: ['облегчённый', 'спокойный', 'довольный', 'удовлетворённый']\r\n  },\r\n  '😍': {\r\n    en: ['heart', 'love', 'smiling', 'eyes', 'adore'],\r\n    ru: ['сердце', 'любовь', 'улыбающийся', 'глаза', 'обожать']\r\n  },\r\n  '🥰': {\r\n    en: ['love', 'hearts', 'affection', 'adoration', 'cuddle'],\r\n    ru: ['любовь', 'сердца', 'нежность', 'обожание', 'обниматься']\r\n  },\r\n  '😙': {\r\n    en: ['kiss', 'love', 'affection', 'flirt'],\r\n    ru: ['поцелуй', 'любовь', 'нежность', 'флирт']\r\n  },\r\n  '😗': {\r\n    en: ['kiss', 'face', 'smile', 'affection'],\r\n    ru: ['поцелуй', 'лицо', 'улыбка', 'нежность']\r\n  },\r\n  '😚': {\r\n    en: ['kiss', 'closed eyes', 'affection', 'love'],\r\n    ru: ['поцелуй', 'закрытые глаза', 'нежность', 'любовь']\r\n  },\r\n  '😘': {\r\n    en: ['kiss', 'love', 'affection', 'flirt'],\r\n    ru: ['поцелуй', 'любовь', 'нежность', 'флирт']\r\n  },\r\n  '😋': {\r\n    en: ['yum', 'delicious', 'tasty', 'savor', 'lick'],\r\n    ru: ['ням', 'вкусно', 'аппетитно', 'наслаждаться', 'лизать']\r\n  },\r\n  '🥸': {\r\n    en: ['disguise', 'glasses', 'funny', 'sneaky', 'face'],\r\n    ru: ['маскировка', 'очки', 'смешной', 'хитрый', 'лицо']\r\n  },\r\n  '😵‍💫': {\r\n    en: ['dizzy', 'spiral eyes', 'confused', 'hypnotized', 'disoriented'],\r\n    ru: ['головокружение', 'спиральные глаза', 'запутанный', 'загипнотизированный', 'дезориентированный']\r\n  },\r\n\r\n  // Face affection\r\n  '😛': {\r\n    en: ['tongue', 'playful', 'cheeky', 'silly'],\r\n    ru: ['язык', 'игривый', 'нахальный', 'глупый']\r\n  },\r\n  '😝': {\r\n    en: ['tongue', 'silly', 'wacky', 'fun'],\r\n    ru: ['язык', 'глупый', 'безумный', 'весёлый']\r\n  },\r\n  '😜': {\r\n    en: ['tongue', 'wink', 'playful', 'fun'],\r\n    ru: ['язык', 'подмигивание', 'игривый', 'весёлый']\r\n  },\r\n  '🤪': {\r\n    en: ['crazy', 'wacky', 'zany', 'quirky'],\r\n    ru: ['сумасшедший', 'безумный', 'чудаковатый', 'странный']\r\n  },\r\n  '😎': {\r\n    en: ['cool', 'sunglasses', 'confident', 'chill'],\r\n    ru: ['крутой', 'очки', 'уверенный', 'расслабленный']\r\n  },\r\n  '🤓': {\r\n    en: ['nerd', 'geek', 'glasses', 'studious'],\r\n    ru: ['ботан', 'задрот', 'очки', 'учёный']\r\n  },\r\n  '🧐': {\r\n    en: ['monocle', 'investigative', 'curious', 'thoughtful'],\r\n    ru: ['одноглазый', 'расследовательский', 'любопытный', 'задумчивый']\r\n  },\r\n  '🤨': {\r\n    en: ['skeptical', 'doubtful', 'uncertain', 'raised eyebrow'],\r\n    ru: ['скептический', 'сомневающийся', 'неуверенный', 'поднятая бровь']\r\n  },\r\n  '🤩': {\r\n    en: ['starstruck', 'amazed', 'excited', 'admire'],\r\n    ru: ['восхищённый', 'поражённый', 'взволнованный', 'обожать']\r\n  },\r\n  '🥳': {\r\n    en: ['party', 'celebrate', 'birthday', 'festive'],\r\n    ru: ['вечеринка', 'праздновать', 'день рождения', 'праздничный']\r\n  },\r\n  '😏': {\r\n    en: ['smirk', 'sly', 'mischievous', 'confident'],\r\n    ru: ['ухмылка', 'хитрый', 'озорной', 'уверенный']\r\n  },\r\n  '😒': {\r\n    en: ['unamused', 'displeased', 'bored', 'sigh'],\r\n    ru: ['неудовлетворённый', 'недовольный', 'скучный', 'вздох']\r\n  },\r\n  '😞': {\r\n    en: ['disappointed', 'sad', 'down', 'somber'],\r\n    ru: ['разочарованный', 'грустный', 'унылый', 'мрачный']\r\n  },\r\n  '😔': {\r\n    en: ['pensive', 'sad', 'reflective', 'mournful'],\r\n    ru: ['задумчивый', 'грустный', 'рефлексивный', 'скорбный']\r\n  },\r\n  '😟': {\r\n    en: ['worried', 'concerned', 'anxious', 'upset'],\r\n    ru: ['обеспокоенный', 'тревожный', 'беспокойный', 'расстроенный']\r\n  },\r\n  '😕': {\r\n    en: ['confused', 'perplexed', 'uncertain', 'baffled'],\r\n    ru: ['смущённый', 'озадаченный', 'неуверенный', 'в замешательстве']\r\n  },\r\n  '🙁': {\r\n    en: ['frowning', 'sad', 'disappointed', 'downcast'],\r\n    ru: ['хмурый', 'грустный', 'разочарованный', 'угнетённый']\r\n  },\r\n  '☹️': {\r\n    en: ['frowning', 'sad', 'unhappy', 'mournful'],\r\n    ru: ['хмурый', 'грустный', 'несчастный', 'скорбный']\r\n  },\r\n  '😣': {\r\n    en: ['strained', 'persevering', 'tired', 'discomfort'],\r\n    ru: ['напряжённый', 'терпеливый', 'уставший', 'дискомфорт']\r\n  },\r\n  '😖': {\r\n    en: ['confounded', 'annoyed', 'distressed', 'exasperated'],\r\n    ru: ['озадаченный', 'раздражённый', 'огорчённый', 'изнурённый']\r\n  },\r\n  '😫': {\r\n    en: ['tired', 'exhausted', 'weary', 'worn out'],\r\n    ru: ['усталый', 'измученный', 'изнурённый', 'измотанный']\r\n  },\r\n\r\n  // Face tongue\r\n  '😩': {\r\n    en: ['weary', 'tired', 'exhausted', 'overwhelmed'],\r\n    ru: ['изнурённый', 'уставший', 'измученный', 'перегруженный']\r\n  },\r\n  '🥺': {\r\n    en: ['pleading', 'begging', 'cute', 'vulnerable'],\r\n    ru: ['умоляющий', 'просьба', 'милый', 'уязвимый']\r\n  },\r\n  '😢': {\r\n    en: ['cry', 'sad', 'tear', 'sorrow'],\r\n    ru: ['плакать', 'грустный', 'слеза', 'печаль']\r\n  },\r\n  '😭': {\r\n    en: ['crying', 'tearful', 'sad', 'heartbroken'],\r\n    ru: ['плачущий', 'со слезами', 'грустный', 'с разбитым сердцем']\r\n  },\r\n  '😤': {\r\n    en: ['triumphant', 'exasperated', 'proud', 'angry'],\r\n    ru: ['триумфальный', 'раздражённый', 'гордый', 'сердитый']\r\n  },\r\n  '😠': {\r\n    en: ['angry', 'mad', 'annoyed', 'irate'],\r\n    ru: ['сердитый', 'злой', 'раздражённый', 'яростный']\r\n  },\r\n  '😡': {\r\n    en: ['pouting', 'mad', 'furious', 'irate'],\r\n    ru: ['надувшийся', 'злой', 'яростный', 'взбешённый']\r\n  },\r\n  '🤬': {\r\n    en: ['cursing', 'swearing', 'angry', 'foul language'],\r\n    ru: ['ругательства', 'мат', 'сердитый', 'неприличный']\r\n  },\r\n  '🤯': {\r\n    en: ['mind blown', 'shocked', 'amazed', 'stunned'],\r\n    ru: ['взрыв мозга', 'шокированный', 'поражённый', 'ошеломлённый']\r\n  },\r\n  '😳': {\r\n    en: ['flushed', 'embarrassed', 'shocked', 'awkward'],\r\n    ru: ['покрасневший', 'смущённый', 'шокированный', 'неловкий']\r\n  },\r\n  '🥵': {\r\n    en: ['hot', 'overheated', 'sweaty', 'exhausted'],\r\n    ru: ['горячий', 'перегретый', 'потный', 'изнурённый']\r\n  },\r\n  '🥶': {\r\n    en: ['cold', 'freezing', 'chilly', 'frozen'],\r\n    ru: ['холодный', 'замерзающий', 'прохладный', 'замороженный']\r\n  },\r\n  '😱': {\r\n    en: ['screaming', 'horror', 'shock', 'fear'],\r\n    ru: ['кричащий', 'ужас', 'шок', 'страх']\r\n  },\r\n  '😨': {\r\n    en: ['fearful', 'scared', 'anxious', 'nervous'],\r\n    ru: ['боязливый', 'испуганный', 'тревожный', 'нервный']\r\n  },\r\n  '😰': {\r\n    en: ['anxious', 'nervous', 'sweating', 'scared'],\r\n    ru: ['тревожный', 'нервный', 'потеющий', 'испуганный']\r\n  },\r\n  '😥': {\r\n    en: ['disappointed', 'sad', 'pensive', 'teary'],\r\n    ru: ['разочарованный', 'грустный', 'задумчивый', 'со слезами']\r\n  },\r\n  '😓': {\r\n    en: ['cold sweat', 'nervous', 'anxious', 'tired'],\r\n    ru: ['холодный пот', 'нервный', 'тревожный', 'усталый']\r\n  },\r\n  '🤗': {\r\n    en: ['hug', 'embrace', 'caring', 'love'],\r\n    ru: ['объятие', 'принимать', 'заботливый', 'любовь']\r\n  },\r\n  '🤔': {\r\n    en: ['thinking', 'pondering', 'curious', 'confused'],\r\n    ru: ['думающий', 'размышляющий', 'любопытный', 'смущённый']\r\n  },\r\n  '🤭': {\r\n    en: ['guilty', 'shy', 'embarrassed', 'oops'],\r\n    ru: ['виноватый', 'застенчивый', 'смущённый', 'упс']\r\n  },\r\n  '🤫': {\r\n    en: ['quiet', 'secret', 'hush', 'shh'],\r\n    ru: ['тихий', 'секрет', 'тишина', 'ш-ш']\r\n  },\r\n\r\n  // Face negative\r\n  '🤥': {\r\n    en: ['lying', 'fib', 'deceitful', 'dishonest'],\r\n    ru: ['врущий', 'ложь', 'обманчивый', 'нечестный']\r\n  },\r\n  '😶': {\r\n    en: ['speechless', 'mute', 'quiet', 'blank'],\r\n    ru: ['безмолвный', 'немой', 'тихий', 'пустой']\r\n  },\r\n  '😐': {\r\n    en: ['neutral', 'expressionless', 'indifferent', 'flat'],\r\n    ru: ['нейтральный', 'без выражения', 'безразличный', 'плоский']\r\n  },\r\n  '😑': {\r\n    en: ['deadpan', 'expressionless', 'blank', 'unemotional'],\r\n    ru: ['без эмоций', 'без выражения', 'пустой', 'неэмоциональный']\r\n  },\r\n  '😬': {\r\n    en: ['grimace', 'awkward', 'nervous', 'tense'],\r\n    ru: ['гримаса', 'неловко', 'нервный', 'напряжённый']\r\n  },\r\n  '🙄': {\r\n    en: ['eye roll', 'sarcastic', 'disdain', 'bored'],\r\n    ru: ['закатывание глаз', 'саркастичный', 'пренебрежительный', 'скучный']\r\n  },\r\n  '😯': {\r\n    en: ['hushed', 'surprised', 'shocked', 'amazed'],\r\n    ru: ['тихий', 'удивлённый', 'шокированный', 'поражённый']\r\n  },\r\n  '😦': {\r\n    en: ['frowning', 'dismayed', 'shocked', 'surprised'],\r\n    ru: ['хмурый', 'огорчённый', 'шокированный', 'удивлённый']\r\n  },\r\n  '😧': {\r\n    en: ['astonished', 'stunned', 'surprised', 'speechless'],\r\n    ru: ['изумлённый', 'ошеломлённый', 'удивлённый', 'безмолвный']\r\n  },\r\n  '😮': {\r\n    en: ['open mouth', 'surprised', 'shocked', 'amazed'],\r\n    ru: ['открытый рот', 'удивлённый', 'шокированный', 'поражённый']\r\n  },\r\n  '😲': {\r\n    en: ['astonished', 'stunned', 'shocked', 'in awe'],\r\n    ru: ['изумлённый', 'ошеломлённый', 'шокированный', 'в благоговении']\r\n  },\r\n  '🥱': {\r\n    en: ['yawning', 'sleepy', 'tired', 'bored'],\r\n    ru: ['зевота', 'сонный', 'усталый', 'скучный']\r\n  },\r\n  '😴': {\r\n    en: ['sleeping', 'tired', 'napping', 'dozing'],\r\n    ru: ['спящий', 'усталый', 'дремлющий', 'засыпающий']\r\n  },\r\n  '🤤': {\r\n    en: ['drooling', 'desire', 'craving', 'hungry'],\r\n    ru: ['слюнявый', 'желание', 'тяга', 'голодный']\r\n  },\r\n  '😪': {\r\n    en: ['sleepy', 'drowsy', 'tired', 'nodding'],\r\n    ru: ['сонный', 'вялый', 'усталый', 'кивающий']\r\n  },\r\n  '😵': {\r\n    en: ['dizzy', 'knocked out', 'stunned', 'confused'],\r\n    ru: ['головокружительный', 'вырубленный', 'ошеломлённый', 'сбитый с толку']\r\n  },\r\n  '🤐': {\r\n    en: ['zipper-mouth', 'secretive', 'quiet', 'mute'],\r\n    ru: ['закрытый рот', 'секретный', 'тихий', 'немой']\r\n  },\r\n  '🥴': {\r\n    en: ['woozy', 'tipsy', 'dizzy', 'unsteady'],\r\n    ru: ['ошеломлённый', 'подошедший', 'головокружительный', 'неустойчивый']\r\n  },\r\n  '🤢': {\r\n    en: ['nauseated', 'sick', 'disgusted', 'vomit'],\r\n    ru: ['тошнотворный', 'больной', 'отвратительный', 'рвота']\r\n  },\r\n  '🤮': {\r\n    en: ['vomiting', 'nauseous', 'sick', 'disgust'],\r\n    ru: ['рвота', 'тошнотворный', 'больной', 'отвратительный']\r\n  },\r\n  '🤧': {\r\n    en: ['sneezing', 'ill', 'sick', 'allergy'],\r\n    ru: ['чихание', 'болен', 'больной', 'аллергия']\r\n  },\r\n\r\n  // Face costume\r\n  '😷': {\r\n    en: ['mask', 'sick', 'ill', 'health'],\r\n    ru: ['маска', 'больной', 'нездоровый', 'здоровье']\r\n  },\r\n  '🤒': {\r\n    en: ['fever', 'sick', 'ill', 'unwell'],\r\n    ru: ['лихорадка', 'больной', 'нездоровый', 'неважно']\r\n  },\r\n  '🤕': {\r\n    en: ['injured', 'hurt', 'bandaged', 'pain'],\r\n    ru: ['раненый', 'повреждённый', 'забинтованный', 'боль']\r\n  },\r\n  '🤑': {\r\n    en: ['money', 'rich', 'greedy', 'cash'],\r\n    ru: ['деньги', 'богатый', 'жадный', 'наличные']\r\n  },\r\n  '🤠': {\r\n    en: ['cowboy', 'hat', 'western', 'fun'],\r\n    ru: ['ковбой', 'шляпа', 'вестерн', 'веселье']\r\n  },\r\n  '😈': {\r\n    en: ['devil', 'mischievous', 'naughty', 'sinister'],\r\n    ru: ['дьявол', 'озорной', 'непослушный', 'зловещий']\r\n  },\r\n  '👿': {\r\n    en: ['angry', 'devil', 'evil', 'fiendish'],\r\n    ru: ['сердитый', 'дьявол', 'злой', 'зловещий']\r\n  },\r\n  '👹': {\r\n    en: ['ogre', 'demon', 'monster', 'scary'],\r\n    ru: ['огр', 'демон', 'монстр', 'страшный']\r\n  },\r\n  '👺': {\r\n    en: ['goblin', 'troll', 'spooky', 'creepy'],\r\n    ru: ['гоблин', 'тролль', 'жуткий', 'страшный']\r\n  },\r\n  '🤡': {\r\n    en: ['clown', 'silly', 'funny', 'circus'],\r\n    ru: ['клоун', 'глупый', 'смешной', 'цирк']\r\n  },\r\n  '💩': {\r\n    en: ['poop', 'crap', 'feces', 'funny'],\r\n    ru: ['какашка', 'дерьмо', 'фекалии', 'смешной']\r\n  },\r\n  '👻': {\r\n    en: ['ghost', 'spirit', 'haunted', 'scary'],\r\n    ru: ['призрак', 'дух', 'обитающий', 'страшный']\r\n  },\r\n  '💀': {\r\n    en: ['skull', 'death', 'creepy', 'spooky'],\r\n    ru: ['череп', 'смерть', 'жуткий', 'страшный']\r\n  },\r\n  '☠️': {\r\n    en: ['skull', 'danger', 'death', 'poison'],\r\n    ru: ['череп', 'опасность', 'смерть', 'яд']\r\n  },\r\n  '👽': {\r\n    en: ['alien', 'extraterrestrial', 'space', 'ufo'],\r\n    ru: ['инопланетянин', 'внеземной', 'космос', 'НЛО']\r\n  },\r\n  '👾': {\r\n    en: ['alien', 'monster', 'video game', 'retro'],\r\n    ru: ['инопланетянин', 'монстр', 'видеоигра', 'ретро']\r\n  },\r\n  '🤖': {\r\n    en: ['robot', 'machine', 'tech', 'android'],\r\n    ru: ['робот', 'машина', 'технология', 'андроид']\r\n  },\r\n  '🎃': {\r\n    en: ['pumpkin', 'halloween', 'spooky', 'festive'],\r\n    ru: ['тыква', 'Хэллоуин', 'жуткий', 'праздничный']\r\n  },\r\n\r\n  // Cat faces\r\n  '😺': {\r\n    en: ['smiling', 'cat', 'happy', 'playful'],\r\n    ru: ['улыбающийся', 'кот', 'счастливый', 'игривый']\r\n  },\r\n  '😸': {\r\n    en: ['grinning', 'cat', 'joyful', 'cheerful'],\r\n    ru: ['широко улыбающийся', 'кот', 'радостный', 'весёлый']\r\n  },\r\n  '😹': {\r\n    en: ['tearful', 'joy', 'cat', 'laughing'],\r\n    ru: ['со слезами', 'радость', 'кот', 'смеющийся']\r\n  },\r\n  '😻': {\r\n    en: ['heart', 'cat', 'love', 'adorable'],\r\n    ru: ['сердце', 'кот', 'любовь', 'милый']\r\n  },\r\n  '😼': {\r\n    en: ['smirking', 'cat', 'mischievous', 'sly'],\r\n    ru: ['ухмыляющийся', 'кот', 'озорной', 'хитрый']\r\n  },\r\n  '😽': {\r\n    en: ['kissing', 'cat', 'affection', 'cute'],\r\n    ru: ['целующий', 'кот', 'нежность', 'милый']\r\n  },\r\n  '🙀': {\r\n    en: ['surprised', 'cat', 'scared', 'shocked'],\r\n    ru: ['испуганный', 'кот', 'испуганный', 'шокированный']\r\n  },\r\n  '😿': {\r\n    en: ['crying', 'cat', 'sad', 'tearful'],\r\n    ru: ['плачущий', 'кот', 'грустный', 'со слезами']\r\n  },\r\n  '😾': {\r\n    en: ['angry', 'cat', 'annoyed', 'displeased'],\r\n    ru: ['сердитый', 'кот', 'раздражённый', 'недовольный']\r\n  },\r\n\r\n  //------------------------- People & Body -------------------------\r\n  // Hand gestures\r\n  '👋': {\r\n    en: ['wave', 'hello', 'goodbye', 'greeting'],\r\n    ru: ['махание', 'привет', 'прощание', 'приветствие']\r\n  },\r\n  '🤚': {\r\n    en: ['raised hand', 'stop', 'palm'],\r\n    ru: ['поднятая рука', 'стой', 'ладонь']\r\n  },\r\n  '🖐️': {\r\n    en: ['hand', 'high five', 'greeting'],\r\n    ru: ['рука', 'дай пять', 'приветствие']\r\n  },\r\n  '✋': {\r\n    en: ['stop', 'palm', 'high five'],\r\n    ru: ['стой', 'ладонь', 'дай пять']\r\n  },\r\n  '🖖': {\r\n    en: ['vulcan salute', 'live long', 'sci-fi'],\r\n    ru: ['салют Вулканцев', 'живи долго', 'научная фантастика']\r\n  },\r\n  '👌': {\r\n    en: ['okay', 'perfect', 'good'],\r\n    ru: ['ок', 'идеально', 'хорошо']\r\n  },\r\n  '🤌': {\r\n    en: ['pinched', 'precise', 'delicious'],\r\n    ru: ['сжатый', 'точный', 'вкусный']\r\n  },\r\n  '🤏': {\r\n    en: ['small', 'tiny', 'minuscule'],\r\n    ru: ['маленький', 'крошечный', 'микроскопический']\r\n  },\r\n  '✌️': {\r\n    en: ['peace', 'victory', 'v sign'],\r\n    ru: ['мир', 'победа', 'знак победы']\r\n  },\r\n  '🤞': {\r\n    en: ['fingers crossed', 'hope', 'luck'],\r\n    ru: ['скрещенные пальцы', 'надежда', 'удача']\r\n  },\r\n  '🤟': {\r\n    en: ['I love you', 'rock on', 'sign language'],\r\n    ru: ['я тебя люблю', 'рок он', 'язык жестов']\r\n  },\r\n  '🤘': {\r\n    en: ['rock', 'metal', 'horns'],\r\n    ru: ['рок', 'металл', 'рога']\r\n  },\r\n  '🤙': {\r\n    en: ['call me', 'hang loose', 'shaka'],\r\n    ru: ['позвони мне', 'расслабься', 'шак']\r\n  },\r\n  '👈': {\r\n    en: ['point left', 'direction', 'arrow'],\r\n    ru: ['указание влево', 'направление', 'стрелка']\r\n  },\r\n  '👉': {\r\n    en: ['point right', 'direction', 'arrow'],\r\n    ru: ['указание вправо', 'направление', 'стрелка']\r\n  },\r\n  '👆': {\r\n    en: ['point up', 'direction', 'up'],\r\n    ru: ['указание вверх', 'направление', 'вверх']\r\n  },\r\n  '🖕': {\r\n    en: ['middle finger', 'offensive', 'rude'],\r\n    ru: ['средний палец', 'оскорбительный', 'грубый']\r\n  },\r\n  '👇': {\r\n    en: ['point down', 'direction', 'down'],\r\n    ru: ['указание вниз', 'направление', 'вниз']\r\n  },\r\n  '☝️': {\r\n    en: ['point up', 'number one', 'important'],\r\n    ru: ['указание вверх', 'номер один', 'важный']\r\n  },\r\n  '👍': {\r\n    en: ['thumbs up', 'good', 'approve'],\r\n    ru: ['палец вверх', 'хорошо', 'одобрить']\r\n  },\r\n  '👎': {\r\n    en: ['thumbs down', 'bad', 'disapprove'],\r\n    ru: ['палец вниз', 'плохо', 'не одобрять']\r\n  },\r\n\r\n  // Hand symbols\r\n  '✊': {\r\n    en: ['fist', 'power', 'solidarity'],\r\n    ru: ['кулак', 'сила', 'солидарность']\r\n  },\r\n  '👊': {\r\n    en: ['punch', 'fist bump', 'hit'],\r\n    ru: ['удар', 'кулачок', 'ударить']\r\n  },\r\n  '🤛': {\r\n    en: ['left fist', 'punch', 'strike'],\r\n    ru: ['левая рука', 'удар', 'нанести удар']\r\n  },\r\n  '🤜': {\r\n    en: ['right fist', 'punch', 'strike'],\r\n    ru: ['правая рука', 'удар', 'нанести удар']\r\n  },\r\n  '👏': {\r\n    en: ['clap', 'applause', 'bravo'],\r\n    ru: ['хлопки', 'аплодисменты', 'браво']\r\n  },\r\n  '🙌': {\r\n    en: ['celebrate', 'praise', 'hooray'],\r\n    ru: ['торжествовать', 'хвалить', 'ура']\r\n  },\r\n  '👐': {\r\n    en: ['open hands', 'embrace', 'welcome'],\r\n    ru: ['раскрытые руки', 'объятие', 'добро пожаловать']\r\n  },\r\n  '🤲': {\r\n    en: ['palms', 'offering', 'receive'],\r\n    ru: ['ладони', 'предложение', 'получать']\r\n  },\r\n  '🤝': {\r\n    en: ['handshake', 'agreement', 'cooperation'],\r\n    ru: ['рукопожатие', 'соглашение', 'сотрудничество']\r\n  },\r\n  '🙏': {\r\n    en: ['pray', 'thanks', 'please'],\r\n    ru: ['молиться', 'спасибо', 'пожалуйста']\r\n  },\r\n\r\n  // Body parts\r\n  '✍️': {\r\n    en: ['writing', 'pen', 'signature'],\r\n    ru: ['письмо', 'ручка', 'подпись']\r\n  },\r\n  '💅': {\r\n    en: ['nail polish', 'beauty', 'manicure'],\r\n    ru: ['лак для ногтей', 'красота', 'маникюр']\r\n  },\r\n  '🤳': {\r\n    en: ['selfie', 'photo', 'camera'],\r\n    ru: ['селфи', 'фото', 'камера']\r\n  },\r\n  '💪': {\r\n    en: ['flex', 'strong', 'muscle'],\r\n    ru: ['сгибать', 'сильный', 'мышцы']\r\n  },\r\n  '🦾': {\r\n    en: ['mechanical arm', 'robotic', 'cyborg'],\r\n    ru: ['механическая рука', 'роботизированный', 'киборг']\r\n  },\r\n  '🦿': {\r\n    en: ['mechanical leg', 'prosthetic', 'robotic'],\r\n    ru: ['механическая нога', 'протез', 'роботизированный']\r\n  },\r\n  '🦵': {\r\n    en: ['leg', 'limb', 'lower body'],\r\n    ru: ['нога', 'конечность', 'нижняя часть тела']\r\n  },\r\n  '🦶': {\r\n    en: ['foot', 'toes', 'step'],\r\n    ru: ['нога', 'пальцы ноги', 'шаг']\r\n  },\r\n  '👂': {\r\n    en: ['ear', 'listening', 'sound'],\r\n    ru: ['ухо', 'слушание', 'звук']\r\n  },\r\n  '🦻': {\r\n    en: ['hearing aid', 'listening', 'assistive'],\r\n    ru: ['слуховой аппарат', 'слушание', 'помощь']\r\n  },\r\n  '👃': {\r\n    en: ['nose', 'smell', 'scent'],\r\n    ru: ['нос', 'запах', 'аромат']\r\n  },\r\n  '🧠': {\r\n    en: ['brain', 'intelligence', 'mind'],\r\n    ru: ['мозг', 'интеллект', 'ум']\r\n  },\r\n  '🫀': {\r\n    en: ['heart (organ)', 'anatomy', 'biology'],\r\n    ru: ['сердце (орган)', 'анатомия', 'биология']\r\n  },\r\n  '🫁': {\r\n    en: ['lungs', 'breath', 'organ'],\r\n    ru: ['легкие', 'дыхание', 'орган']\r\n  },\r\n  '🦷': {\r\n    en: ['tooth', 'dental', 'smile'],\r\n    ru: ['зуб', 'стоматология', 'улыбка']\r\n  },\r\n  '🦴': {\r\n    en: ['bone', 'skeleton', 'hard'],\r\n    ru: ['кость', 'скелет', 'твердый']\r\n  },\r\n  '👀': {\r\n    en: ['eyes', 'look', 'see'],\r\n    ru: ['глаза', 'смотреть', 'видеть']\r\n  },\r\n  '👁️': {\r\n    en: ['eye', 'vision', 'watch'],\r\n    ru: ['глаз', 'зрение', 'наблюдать']\r\n  },\r\n  '👅': {\r\n    en: ['tongue', 'taste', 'lick'],\r\n    ru: ['язык', 'вкус', 'лизать']\r\n  },\r\n  '👄': {\r\n    en: ['lips', 'kiss', 'mouth'],\r\n    ru: ['губы', 'поцелуй', 'рот']\r\n  },\r\n\r\n  // Person\r\n  '👶': {\r\n    en: ['baby', 'infant', 'cute'],\r\n    ru: ['младенец', 'ребенок', 'милый']\r\n  },\r\n  '🧒': {\r\n    en: ['child', 'kid', 'youth'],\r\n    ru: ['ребенок', 'малыш', 'юный']\r\n  },\r\n  '👦': {\r\n    en: ['boy', 'child', 'kid'],\r\n    ru: ['мальчик', 'ребенок', 'малыш']\r\n  },\r\n  '👧': {\r\n    en: ['girl', 'child', 'kid'],\r\n    ru: ['девочка', 'ребенок', 'малышка']\r\n  },\r\n  '🧑': {\r\n    en: ['person', 'human', 'individual'],\r\n    ru: ['человек', 'личность', 'индивид']\r\n  },\r\n  '👱': {\r\n    en: ['blonde', 'person', 'light hair'],\r\n    ru: ['блондин', 'человек', 'светлые волосы']\r\n  },\r\n  '👨': {\r\n    en: ['man', 'male', 'guy'],\r\n    ru: ['мужчина', 'мужской', 'парень']\r\n  },\r\n  '🧔': {\r\n    en: ['bearded', 'man', 'facial hair'],\r\n    ru: ['бородатый', 'мужчина', 'борода']\r\n  },\r\n  '👩': {\r\n    en: ['woman', 'female', 'lady'],\r\n    ru: ['женщина', 'женский', 'дама']\r\n  },\r\n  '🧓': {\r\n    en: ['elderly', 'senior', 'aged'],\r\n    ru: ['пожилой', 'старший', 'в преклонном возрасте']\r\n  },\r\n  '👴': {\r\n    en: ['old man', 'elderly', 'senior'],\r\n    ru: ['старик', 'пожилой', 'старший']\r\n  },\r\n  '👵': {\r\n    en: ['old woman', 'elderly', 'senior'],\r\n    ru: ['старуха', 'пожилая', 'старшая']\r\n  },\r\n  '🙍': {\r\n    en: ['frowning', 'sad', 'displeased'],\r\n    ru: ['хмурый', 'грустный', 'недовольный']\r\n  },\r\n  '🙎': {\r\n    en: ['pouting', 'angry', 'displeased'],\r\n    ru: ['надувшийся', 'сердитый', 'недовольный']\r\n  },\r\n  '🙅': {\r\n    en: ['no', 'prohibited', 'refusal'],\r\n    ru: ['нет', 'запрещено', 'отказ']\r\n  },\r\n  '🙆': {\r\n    en: ['ok', 'acceptable', 'okay'],\r\n    ru: ['ок', 'приемлемо', 'хорошо']\r\n  },\r\n  '💁': {\r\n    en: ['information', 'help', 'assistance'],\r\n    ru: ['информация', 'помощь', 'поддержка']\r\n  },\r\n  '🙋': {\r\n    en: ['raising hand', 'question', 'volunteer'],\r\n    ru: ['поднимающая руку', 'вопрос', 'доброволец']\r\n  },\r\n  '🧏': {\r\n    en: ['deaf', 'listening', 'silent'],\r\n    ru: ['глухой', 'слушающий', 'безмолвный']\r\n  },\r\n  '🙇': {\r\n    en: ['bowing', 'apologetic', 'respect'],\r\n    ru: ['наклон', 'извиняющийся', 'уважение']\r\n  },\r\n  '🤦': {\r\n    en: ['facepalm', 'disbelief', 'oops'],\r\n    ru: ['лицо ладонь', 'недоверие', 'упс']\r\n  },\r\n  '🤷': {\r\n    en: ['shrug', 'uncertain', 'indifferent'],\r\n    ru: ['пожимание плечами', 'неуверенный', 'безразличный']\r\n  },\r\n\r\n  // Professional\r\n  '👮': {\r\n    en: ['police', 'officer', 'law'],\r\n    ru: ['полицейский', 'офицер', 'закон']\r\n  },\r\n  '🕵️': {\r\n    en: ['detective', 'spy', 'investigate'],\r\n    ru: ['детектив', 'шпион', 'расследование']\r\n  },\r\n  '💂': {\r\n    en: ['guard', 'soldier', 'military'],\r\n    ru: ['страж', 'солдат', 'военный']\r\n  },\r\n  '🥷': {\r\n    en: ['ninja', 'stealth', 'assassin'],\r\n    ru: ['ниндзя', 'скрытность', 'ассассин']\r\n  },\r\n  '👷': {\r\n    en: ['construction', 'worker', 'helmet'],\r\n    ru: ['строитель', 'рабочий', 'шлем']\r\n  },\r\n  '🤴': {\r\n    en: ['prince', 'royalty', 'king'],\r\n    ru: ['принц', 'королевская семья', 'король']\r\n  },\r\n  '👸': {\r\n    en: ['princess', 'royalty', 'queen'],\r\n    ru: ['принцесса', 'королевская семья', 'королева']\r\n  },\r\n  '👳': {\r\n    en: ['turban', 'cultural', 'tradition'],\r\n    ru: ['тюрбан', 'культура', 'традиция']\r\n  },\r\n  '👲': {\r\n    en: ['man with cap', 'cultural', 'traditional'],\r\n    ru: ['мужчина в кепке', 'культура', 'традиционный']\r\n  },\r\n  '🧕': {\r\n    en: ['woman with headscarf', 'cultural', 'modest'],\r\n    ru: ['женщина в платке', 'культурная', 'скромная']\r\n  },\r\n  '🤵': {\r\n    en: ['tuxedo', 'groom', 'formal'],\r\n    ru: ['смокинг', 'жених', 'официальный']\r\n  },\r\n  '👰': {\r\n    en: ['bride', 'wedding', 'formal'],\r\n    ru: ['невеста', 'свадьба', 'официальный']\r\n  },\r\n  '🤰': {\r\n    en: ['pregnant', 'expecting', 'mother'],\r\n    ru: ['беременная', 'ожидающая', 'мать']\r\n  },\r\n  '🤱': {\r\n    en: ['nursing', 'mother', 'baby'],\r\n    ru: ['кормящая', 'мать', 'ребенок']\r\n  },\r\n  '👼': {\r\n    en: ['angel', 'cherub', 'divine'],\r\n    ru: ['ангел', 'херувим', 'божественный']\r\n  },\r\n  '🎅': {\r\n    en: ['santa', 'christmas', 'jolly'],\r\n    ru: ['Санта', 'Рождество', 'радостный']\r\n  },\r\n  '🤶': {\r\n    en: ['mrs claus', 'christmas', 'holiday'],\r\n    ru: ['миссис Клаус', 'Рождество', 'праздник']\r\n  },\r\n  '🦸': {\r\n    en: ['superhero', 'power', 'hero'],\r\n    ru: ['супергерой', 'сила', 'герой']\r\n  },\r\n  '🦹': {\r\n    en: ['villain', 'bad', 'criminal'],\r\n    ru: ['злодей', 'плохой', 'преступник']\r\n  },\r\n  '🧙': {\r\n    en: ['wizard', 'magic', 'sorcery'],\r\n    ru: ['волшебник', 'магия', 'колдовство']\r\n  },\r\n  '🧚': {\r\n    en: ['fairy', 'magic', 'mystical'],\r\n    ru: ['фея', 'магия', 'мистический']\r\n  },\r\n  '🧛': {\r\n    en: ['vampire', 'dracula', 'undead'],\r\n    ru: ['вампир', 'Дракула', 'нежить']\r\n  },\r\n  '🧜': {\r\n    en: ['mermaid', 'mythical', 'ocean'],\r\n    ru: ['русалка', 'мифическая', 'океан']\r\n  },\r\n\r\n  //------------------------- Animals & Nature -------------------------\r\n  // Mammals\r\n  '🐵': {\r\n    en: ['monkey', 'ape', 'funny', 'mammal'],\r\n    ru: ['обезьяна', 'примат', 'смешной', 'млекопитающее']\r\n  },\r\n  '🐒': {\r\n    en: ['monkey', 'primate', 'curious'],\r\n    ru: ['обезьяна', 'примат', 'любопытный']\r\n  },\r\n  '🦍': {\r\n    en: ['gorilla', 'ape', 'strong', 'wild'],\r\n    ru: ['горилла', 'обезьяна', 'сильный', 'дикий']\r\n  },\r\n  '🦧': {\r\n    en: ['orangutan', 'ape', 'wild', 'mammal'],\r\n    ru: ['орангутан', 'обезьяна', 'дикий', 'млекопитающее']\r\n  },\r\n  '🐶': {\r\n    en: ['dog', 'puppy', 'pet', 'mammal'],\r\n    ru: ['собака', 'щенок', 'домашний питомец', 'млекопитающее']\r\n  },\r\n  '🐕': {\r\n    en: ['dog', 'canine', 'pet'],\r\n    ru: ['собака', 'псовой', 'домашний питомец']\r\n  },\r\n  '🦮': {\r\n    en: ['guide dog', 'service', 'assistance'],\r\n    ru: ['собака-поводырь', 'служебная', 'помощь']\r\n  },\r\n  '🐕‍🦺': {\r\n    en: ['service dog', 'working', 'assistance'],\r\n    ru: ['служебная собака', 'рабочая', 'помощь']\r\n  },\r\n  '🐩': {\r\n    en: ['poodle', 'dog', 'pet', 'fancy'],\r\n    ru: ['пудель', 'собака', 'домашний питомец', 'элегантный']\r\n  },\r\n  '🐺': {\r\n    en: ['wolf', 'wild', 'howl'],\r\n    ru: ['волк', 'дикий', 'воет']\r\n  },\r\n  '🦊': {\r\n    en: ['fox', 'cunning', 'wild'],\r\n    ru: ['лиса', 'хитрая', 'дикая']\r\n  },\r\n  '🦝': {\r\n    en: ['raccoon', 'mischievous', 'wild'],\r\n    ru: ['енот', 'озорной', 'дикий']\r\n  },\r\n  '🐱': {\r\n    en: ['cat', 'pet', 'feline'],\r\n    ru: ['кот', 'домашний питомец', 'кошачий']\r\n  },\r\n  '🐈': {\r\n    en: ['cat', 'feline', 'pet'],\r\n    ru: ['кот', 'кошачий', 'домашний питомец']\r\n  },\r\n  '🐈‍⬛': {\r\n    en: ['black cat', 'mysterious', 'feline'],\r\n    ru: ['чёрный кот', 'загадочный', 'кошачий']\r\n  },\r\n  '🦁': {\r\n    en: ['lion', 'king', 'wild', 'courage'],\r\n    ru: ['лев', 'король', 'дикий', 'отвага']\r\n  },\r\n  '🐯': {\r\n    en: ['tiger', 'wild', 'stripes', 'fierce'],\r\n    ru: ['тигр', 'дикий', 'полосатый', 'свирепый']\r\n  },\r\n  '🐅': {\r\n    en: ['tiger', 'stripes', 'wild'],\r\n    ru: ['тигр', 'полосатый', 'дикий']\r\n  },\r\n  '🐆': {\r\n    en: ['leopard', 'spots', 'wild', 'fast'],\r\n    ru: ['леопард', 'пятна', 'дикий', 'быстрый']\r\n  },\r\n  '🐴': {\r\n    en: ['horse', 'ride', 'equine'],\r\n    ru: ['лошадь', 'езда', 'конный']\r\n  },\r\n  '🐎': {\r\n    en: ['horse', 'racing', 'equine'],\r\n    ru: ['лошадь', 'гонки', 'конный']\r\n  },\r\n  '🦄': {\r\n    en: ['unicorn', 'magical', 'fantasy'],\r\n    ru: ['единорог', 'волшебный', 'фэнтези']\r\n  },\r\n  '🦓': {\r\n    en: ['zebra', 'stripes', 'wild'],\r\n    ru: ['зебра', 'полосатая', 'дикая']\r\n  },\r\n  '🦌': {\r\n    en: ['deer', 'antlers', 'forest'],\r\n    ru: ['олень', 'рога', 'лес']\r\n  },\r\n  '🦬': {\r\n    en: ['bison', 'buffalo', 'wild'],\r\n    ru: ['бизон', 'буйвол', 'дикий']\r\n  },\r\n  '🐮': {\r\n    en: ['cow', 'farm', 'bovine'],\r\n    ru: ['корова', 'ферма', 'коровий']\r\n  },\r\n  '🐂': {\r\n    en: ['ox', 'bull', 'bovine'],\r\n    ru: ['вол', 'бык', 'коровий']\r\n  },\r\n  '🐃': {\r\n    en: ['water buffalo', 'bovine', 'farm'],\r\n    ru: ['водный буйвол', 'коровий', 'ферма']\r\n  },\r\n  '🐄': {\r\n    en: ['cow', 'bovine', 'farm'],\r\n    ru: ['корова', 'коровий', 'ферма']\r\n  },\r\n  '🐷': {\r\n    en: ['pig', 'farm', 'oink'],\r\n    ru: ['свинья', 'ферма', 'хрюк']\r\n  },\r\n  '🐖': {\r\n    en: ['pig', 'swine', 'farm'],\r\n    ru: ['свинья', 'свинное животное', 'ферма']\r\n  },\r\n  '🐗': {\r\n    en: ['boar', 'wild', 'pig'],\r\n    ru: ['кабан', 'дикий', 'свинья']\r\n  },\r\n  '🐽': {\r\n    en: ['pig nose', 'snout'],\r\n    ru: ['свинной нос', 'хоботок']\r\n  },\r\n  '🐏': {\r\n    en: ['ram', 'sheep', 'male'],\r\n    ru: ['баран', 'овца', 'самец']\r\n  },\r\n  '🐑': {\r\n    en: ['sheep', 'wool', 'farm'],\r\n    ru: ['овца', 'шерсть', 'ферма']\r\n  },\r\n  '🐐': {\r\n    en: ['goat', 'farm', 'bleat'],\r\n    ru: ['коза', 'ферма', 'блеет']\r\n  },\r\n  '🐪': {\r\n    en: ['camel', 'desert', 'hump'],\r\n    ru: ['верблюд', 'пустыня', 'горб']\r\n  },\r\n  '🐫': {\r\n    en: ['camel', 'two-hump', 'desert'],\r\n    ru: ['двугорбый верблюд', 'двугорбый', 'пустыня']\r\n  },\r\n  '🦙': {\r\n    en: ['llama', 'alpaca', 'cute'],\r\n    ru: ['лама', 'альпака', 'милый']\r\n  },\r\n  '🦒': {\r\n    en: ['giraffe', 'tall', 'spots'],\r\n    ru: ['жираф', 'высокий', 'пятна']\r\n  },\r\n  '🐘': {\r\n    en: ['elephant', 'trunk', 'large'],\r\n    ru: ['слон', 'хобот', 'большой']\r\n  },\r\n  '🦏': {\r\n    en: ['rhinoceros', 'horn', 'tough'],\r\n    ru: ['носорог', 'рог', 'жесткий']\r\n  },\r\n  '🦛': {\r\n    en: ['hippopotamus', 'water', 'large'],\r\n    ru: ['бегемот', 'вода', 'большой']\r\n  },\r\n\r\n  // Birds\r\n  '🦃': {\r\n    en: ['turkey', 'bird', 'thanksgiving'],\r\n    ru: ['индейка', 'птица', 'День благодарения']\r\n  },\r\n  '🐔': {\r\n    en: ['chicken', 'rooster', 'hen'],\r\n    ru: ['курица', 'петух', 'курица (самка)']\r\n  },\r\n  '🐓': {\r\n    en: ['rooster', 'chicken', 'bird'],\r\n    ru: ['петух', 'курица', 'птица']\r\n  },\r\n  '🐣': {\r\n    en: ['hatching chick', 'baby', 'bird'],\r\n    ru: ['вылупляющийся цыплёнок', 'малыш', 'птица']\r\n  },\r\n  '🐤': {\r\n    en: ['chick', 'small', 'bird'],\r\n    ru: ['цыплёнок', 'маленький', 'птица']\r\n  },\r\n  '🐥': {\r\n    en: ['baby chicken', 'cute', 'bird'],\r\n    ru: ['цыплёнок', 'милый', 'птица']\r\n  },\r\n  '🐦': {\r\n    en: ['bird', 'tweet', 'wing'],\r\n    ru: ['птица', 'чирик', 'крыло']\r\n  },\r\n  '🐧': {\r\n    en: ['penguin', 'cold', 'bird'],\r\n    ru: ['пингвин', 'холодный', 'птица']\r\n  },\r\n  '🕊️': {\r\n    en: ['dove', 'peace', 'bird'],\r\n    ru: ['голубь', 'мир', 'птица']\r\n  },\r\n  '🦅': {\r\n    en: ['eagle', 'wild', 'bird'],\r\n    ru: ['орёл', 'дикий', 'птица']\r\n  },\r\n  '🦆': {\r\n    en: ['duck', 'water', 'bird'],\r\n    ru: ['утка', 'вода', 'птица']\r\n  },\r\n  '🦢': {\r\n    en: ['swan', 'graceful', 'bird'],\r\n    ru: ['лебедь', 'грациозный', 'птица']\r\n  },\r\n  '🦉': {\r\n    en: ['owl', 'wise', 'night', 'bird'],\r\n    ru: ['сова', 'мудрая', 'ночная', 'птица']\r\n  },\r\n  '🦤': {\r\n    en: ['dodo', 'extinct', 'bird'],\r\n    ru: ['додо', 'вымершая', 'птица']\r\n  },\r\n  '🪶': {\r\n    en: ['feather', 'light', 'bird'],\r\n    ru: ['перо', 'лёгкое', 'птица']\r\n  },\r\n  '🦩': {\r\n    en: ['flamingo', 'pink', 'bird'],\r\n    ru: ['фламинго', 'розовый', 'птица']\r\n  },\r\n  '🦚': {\r\n    en: ['peacock', 'colorful', 'bird'],\r\n    ru: ['павлин', 'яркий', 'птица']\r\n  },\r\n  '🦜': {\r\n    en: ['parrot', 'talkative', 'colorful'],\r\n    ru: ['попугай', 'болтливый', 'яркий']\r\n  },\r\n\r\n  // Reptiles/Amphibians\r\n  '🐸': {\r\n    en: ['frog', 'amphibian', 'green'],\r\n    ru: ['лягушка', 'амфибия', 'зелёная']\r\n  },\r\n  '🐊': {\r\n    en: ['crocodile', 'reptile', 'danger'],\r\n    ru: ['крокодил', 'рептилия', 'опасность']\r\n  },\r\n  '🐢': {\r\n    en: ['turtle', 'slow', 'reptile'],\r\n    ru: ['черепаха', 'медленная', 'рептилия']\r\n  },\r\n  '🦎': {\r\n    en: ['lizard', 'reptile', 'scaly'],\r\n    ru: ['ящерица', 'рептилия', 'чешуйчатая']\r\n  },\r\n  '🐍': {\r\n    en: ['snake', 'reptile', 'slither'],\r\n    ru: ['змея', 'рептилия', 'ползать']\r\n  },\r\n  '🐲': {\r\n    en: ['dragon face', 'mythical', 'dragon'],\r\n    ru: ['лицо дракона', 'мифический', 'дракон']\r\n  },\r\n  '🐉': {\r\n    en: ['dragon', 'mythical', 'fire'],\r\n    ru: ['дракон', 'мифический', 'огонь']\r\n  },\r\n  '🦕': {\r\n    en: ['dinosaur', 'sauropod', 'prehistoric'],\r\n    ru: ['динозавр', 'зауропод', 'доисторический']\r\n  },\r\n  '🦖': {\r\n    en: ['dinosaur', 'T-Rex', 'prehistoric'],\r\n    ru: ['динозавр', 'Ти-Рекс', 'доисторический']\r\n  },\r\n\r\n  // Marine\r\n  '🐳': {\r\n    en: ['whale', 'ocean', 'large'],\r\n    ru: ['кит', 'океан', 'большой']\r\n  },\r\n  '🐋': {\r\n    en: ['whale', 'ocean', 'big'],\r\n    ru: ['кит', 'океан', 'огромный']\r\n  },\r\n  '🐬': {\r\n    en: ['dolphin', 'ocean', 'friendly'],\r\n    ru: ['дельфин', 'океан', 'дружелюбный']\r\n  },\r\n  '🦭': {\r\n    en: ['seal', 'marine', 'cute'],\r\n    ru: ['тюлень', 'морской', 'милый']\r\n  },\r\n  '🐟': {\r\n    en: ['fish', 'ocean', 'swim'],\r\n    ru: ['рыба', 'океан', 'плавать']\r\n  },\r\n  '🐠': {\r\n    en: ['tropical fish', 'ocean', 'colorful'],\r\n    ru: ['тропическая рыба', 'океан', 'яркая']\r\n  },\r\n  '🐡': {\r\n    en: ['blowfish', 'puffer', 'ocean'],\r\n    ru: ['рыба-иглобрюх', 'фугу', 'океан']\r\n  },\r\n  '🦈': {\r\n    en: ['shark', 'ocean', 'dangerous'],\r\n    ru: ['акула', 'океан', 'опасная']\r\n  },\r\n  '🐙': {\r\n    en: ['octopus', 'marine', 'tentacles'],\r\n    ru: ['осьминог', 'морской', 'щупальца']\r\n  },\r\n  '🐚': {\r\n    en: ['shell', 'beach', 'ocean'],\r\n    ru: ['ракушка', 'пляж', 'океан']\r\n  },\r\n  '🪸': {\r\n    en: ['coral', 'reef', 'ocean'],\r\n    ru: ['коралл', 'риф', 'океан']\r\n  },\r\n\r\n  // Insects\r\n  '🐌': {\r\n    en: ['snail', 'slow', 'mollusk'],\r\n    ru: ['улитка', 'медленная', 'моллюск']\r\n  },\r\n  '🦋': {\r\n    en: ['butterfly', 'insect', 'colorful'],\r\n    ru: ['бабочка', 'насекомое', 'яркая']\r\n  },\r\n  '🐛': {\r\n    en: ['caterpillar', 'insect', 'larva'],\r\n    ru: ['гусеница', 'насекомое', 'личинка']\r\n  },\r\n  '🐜': {\r\n    en: ['ant', 'small', 'insect'],\r\n    ru: ['муравей', 'маленький', 'насекомое']\r\n  },\r\n  '🐝': {\r\n    en: ['bee', 'insect', 'honey'],\r\n    ru: ['пчела', 'насекомое', 'мёд']\r\n  },\r\n  '🪲': {\r\n    en: ['beetle', 'insect', 'bug'],\r\n    ru: ['жук', 'насекомое', 'баг']\r\n  },\r\n  '🐞': {\r\n    en: ['ladybug', 'insect', 'lucky'],\r\n    ru: ['божья коровка', 'насекомое', 'счастливая']\r\n  },\r\n  '🦗': {\r\n    en: ['cricket', 'insect', 'chirp'],\r\n    ru: ['сверчок', 'насекомое', 'щебет']\r\n  },\r\n  '🪳': {\r\n    en: ['cockroach', 'insect', 'pest'],\r\n    ru: ['таракан', 'насекомое', 'вредитель']\r\n  },\r\n  '🕷️': {\r\n    en: ['spider', 'arachnid', 'insect'],\r\n    ru: ['паук', 'паукообразное', 'насекомое']\r\n  },\r\n  '🕸️': {\r\n    en: ['web', 'spider', 'trap'],\r\n    ru: ['паутина', 'паук', 'ловушка']\r\n  },\r\n  '🦂': {\r\n    en: ['scorpion', 'insect', 'venom'],\r\n    ru: ['скорпион', 'насекомое', 'яд']\r\n  },\r\n  '🦟': {\r\n    en: ['mosquito', 'insect', 'bite'],\r\n    ru: ['комар', 'насекомое', 'укус']\r\n  },\r\n  '🪰': {\r\n    en: ['fly', 'insect', 'buzz'],\r\n    ru: ['муха', 'насекомое', 'жужжание']\r\n  },\r\n  '🪱': {\r\n    en: ['worm', 'earth', 'invertebrate'],\r\n    ru: ['червь', 'земля', 'беспозвоночное']\r\n  },\r\n\r\n  // Plants\r\n  '🌸': {\r\n    en: ['cherry blossom', 'flower', 'spring'],\r\n    ru: ['сакура', 'цветок', 'весна']\r\n  },\r\n  '💮': {\r\n    en: ['white flower', 'flower', 'symbol'],\r\n    ru: ['белый цветок', 'цветок', 'символ']\r\n  },\r\n  '🏵️': {\r\n    en: ['rosette', 'flower', 'decorative'],\r\n    ru: ['розетка', 'цветок', 'декоративный']\r\n  },\r\n  '🌹': {\r\n    en: ['rose', 'flower', 'love', 'romance'],\r\n    ru: ['роза', 'цветок', 'любовь', 'романтика']\r\n  },\r\n  '🥀': {\r\n    en: ['wilted flower', 'sad', 'decay'],\r\n    ru: ['увядший цветок', 'грусть', 'разложение']\r\n  },\r\n  '🌺': {\r\n    en: ['hibiscus', 'flower', 'tropical'],\r\n    ru: ['гибискус', 'цветок', 'тропический']\r\n  },\r\n  '🌻': {\r\n    en: ['sunflower', 'flower', 'summer'],\r\n    ru: ['подсолнух', 'цветок', 'лето']\r\n  },\r\n  '🌼': {\r\n    en: ['blossom', 'flower', 'spring'],\r\n    ru: ['цветение', 'цветок', 'весна']\r\n  },\r\n  '🌷': {\r\n    en: ['tulip', 'flower', 'spring'],\r\n    ru: ['тюльпан', 'цветок', 'весна']\r\n  },\r\n  '🌱': {\r\n    en: ['seedling', 'plant', 'growth'],\r\n    ru: ['сеянец', 'растение', 'рост']\r\n  },\r\n  '🪴': {\r\n    en: ['potted plant', 'indoor', 'green'],\r\n    ru: ['растение в горшке', 'в помещении', 'зелёное']\r\n  },\r\n  '🌲': {\r\n    en: ['evergreen', 'tree', 'forest'],\r\n    ru: ['вечнозелёное', 'дерево', 'лес']\r\n  },\r\n  '🌳': {\r\n    en: ['tree', 'nature', 'shade'],\r\n    ru: ['дерево', 'природа', 'тень']\r\n  },\r\n  '🌴': {\r\n    en: ['palm tree', 'tropical', 'beach'],\r\n    ru: ['пальма', 'тропический', 'пляж']\r\n  },\r\n  '🌵': {\r\n    en: ['cactus', 'desert', 'succulent'],\r\n    ru: ['кактус', 'пустыня', 'суккулент']\r\n  },\r\n  '🌾': {\r\n    en: ['sheaf', 'grain', 'farm'],\r\n    ru: ['сноп', 'зерно', 'ферма']\r\n  },\r\n  '🌿': {\r\n    en: ['herb', 'plant', 'leaf'],\r\n    ru: ['трава', 'растение', 'лист']\r\n  },\r\n  '☘️': {\r\n    en: ['shamrock', 'luck', 'clover'],\r\n    ru: ['клевер', 'удача', 'трилистник']\r\n  },\r\n  '🍀': {\r\n    en: ['four-leaf clover', 'luck', 'green'],\r\n    ru: ['клевер с четырьмя листьями', 'удача', 'зелёный']\r\n  },\r\n  '🍁': {\r\n    en: ['maple leaf', 'autumn', 'fall'],\r\n    ru: ['кленовый лист', 'осень', 'осенний']\r\n  },\r\n  '🍂': {\r\n    en: ['fallen leaf', 'autumn', 'nature'],\r\n    ru: ['опавший лист', 'осень', 'природа']\r\n  },\r\n  '🍃': {\r\n    en: ['leaf', 'wind', 'nature'],\r\n    ru: ['лист', 'ветер', 'природа']\r\n  },\r\n\r\n  //------------------------- Food & Drink -------------------------\r\n  // Fruits\r\n  '🍎': {\r\n    en: ['apple', 'red', 'fruit', 'healthy'],\r\n    ru: ['яблоко', 'красное', 'фрукт', 'полезное']\r\n  },\r\n  '🍐': {\r\n    en: ['pear', 'fruit', 'green', 'juicy'],\r\n    ru: ['груша', 'фрукт', 'зелёная', 'сочная']\r\n  },\r\n  '🍊': {\r\n    en: ['orange', 'fruit', 'citrus', 'vitamin C'],\r\n    ru: ['апельсин', 'фрукт', 'цитрус', 'витамин C']\r\n  },\r\n  '🍋': {\r\n    en: ['lemon', 'citrus', 'sour', 'yellow'],\r\n    ru: ['лимон', 'цитрус', 'кислый', 'жёлтый']\r\n  },\r\n  '🍌': {\r\n    en: ['banana', 'fruit', 'yellow', 'tropical'],\r\n    ru: ['банан', 'фрукт', 'жёлтый', 'тропический']\r\n  },\r\n  '🍉': {\r\n    en: ['watermelon', 'fruit', 'summer', 'refreshing'],\r\n    ru: ['арбуз', 'фрукт', 'лето', 'освежающий']\r\n  },\r\n  '🍇': {\r\n    en: ['grapes', 'fruit', 'purple', 'vine'],\r\n    ru: ['виноград', 'фрукт', 'фиолетовый', 'виноградная лоза']\r\n  },\r\n  '🍓': {\r\n    en: ['strawberry', 'fruit', 'red', 'sweet'],\r\n    ru: ['клубника', 'фрукт', 'красная', 'сладкая']\r\n  },\r\n  '🫐': {\r\n    en: ['blueberry', 'fruit', 'blue', 'healthy'],\r\n    ru: ['черника', 'фрукт', 'синяя', 'полезная']\r\n  },\r\n  '🍈': {\r\n    en: ['melon', 'fruit', 'sweet', 'green'],\r\n    ru: ['дыня', 'фрукт', 'сладкая', 'зелёная']\r\n  },\r\n  '🍒': {\r\n    en: ['cherry', 'fruit', 'red', 'sweet'],\r\n    ru: ['вишня', 'фрукт', 'красная', 'сладкая']\r\n  },\r\n  '🍑': {\r\n    en: ['peach', 'fruit', 'fuzzy', 'sweet'],\r\n    ru: ['персик', 'фрукт', 'шероховатый', 'сладкий']\r\n  },\r\n  '🥭': {\r\n    en: ['mango', 'fruit', 'tropical', 'yellow'],\r\n    ru: ['манго', 'фрукт', 'тропический', 'жёлтый']\r\n  },\r\n  '🍍': {\r\n    en: ['pineapple', 'fruit', 'tropical', 'spiky'],\r\n    ru: ['ананас', 'фрукт', 'тропический', 'колючий']\r\n  },\r\n  '🥥': {\r\n    en: ['coconut', 'tropical', 'fruit', 'white'],\r\n    ru: ['кокос', 'тропический', 'фрукт', 'белый']\r\n  },\r\n  '🥝': {\r\n    en: ['kiwi', 'fruit', 'green', 'tart'],\r\n    ru: ['киви', 'фрукт', 'зелёный', 'кисловатый']\r\n  },\r\n\r\n  // Vegetables\r\n  '🍅': {\r\n    en: ['tomato', 'vegetable', 'red', 'juicy'],\r\n    ru: ['помидор', 'овощ', 'красный', 'сочный']\r\n  },\r\n  '🍆': {\r\n    en: ['eggplant', 'vegetable', 'purple', 'aubergine'],\r\n    ru: ['баклажан', 'овощ', 'фиолетовый', 'баклажан']\r\n  },\r\n  '🥑': {\r\n    en: ['avocado', 'vegetable', 'green', 'healthy'],\r\n    ru: ['авокадо', 'овощ', 'зелёный', 'полезный']\r\n  },\r\n  '🥦': {\r\n    en: ['broccoli', 'vegetable', 'green', 'healthy'],\r\n    ru: ['брокколи', 'овощ', 'зелёный', 'полезный']\r\n  },\r\n  '🥬': {\r\n    en: ['leafy greens', 'vegetable', 'lettuce', 'healthy'],\r\n    ru: ['листья салата', 'овощ', 'салат', 'полезный']\r\n  },\r\n  '🥒': {\r\n    en: ['cucumber', 'vegetable', 'green', 'fresh'],\r\n    ru: ['огурец', 'овощ', 'зелёный', 'свежий']\r\n  },\r\n  '🌶️': {\r\n    en: ['chili pepper', 'spicy', 'red', 'hot'],\r\n    ru: ['чили', 'острый', 'красный', 'горячий']\r\n  },\r\n  '🫑': {\r\n    en: ['bell pepper', 'vegetable', 'colorful', 'sweet'],\r\n    ru: ['болгарский перец', 'овощ', 'разноцветный', 'сладкий']\r\n  },\r\n  '🥕': {\r\n    en: ['carrot', 'vegetable', 'orange', 'crunchy'],\r\n    ru: ['морковь', 'овощ', 'оранжевая', 'хрустящая']\r\n  },\r\n  '🧄': {\r\n    en: ['garlic', 'vegetable', 'aromatic', 'flavorful'],\r\n    ru: ['чеснок', 'овощ', 'ароматный', 'вкусный']\r\n  },\r\n  '🧅': {\r\n    en: ['onion', 'vegetable', 'strong', 'flavor'],\r\n    ru: ['лук', 'овощ', 'сильный', 'вкус']\r\n  },\r\n  '🥔': {\r\n    en: ['potato', 'vegetable', 'starchy', 'brown'],\r\n    ru: ['картофель', 'овощ', 'крахмалистый', 'коричневый']\r\n  },\r\n  '🍠': {\r\n    en: ['sweet potato', 'vegetable', 'orange', 'starchy'],\r\n    ru: ['батат', 'овощ', 'оранжевый', 'крахмалистый']\r\n  },\r\n\r\n  // Breads & Baked Goods\r\n  '🥐': {\r\n    en: ['croissant', 'bread', 'pastry', 'flaky'],\r\n    ru: ['круассан', 'хлеб', 'выпечка', 'слоёный']\r\n  },\r\n  '🥯': {\r\n    en: ['bagel', 'bread', 'round', 'chewy'],\r\n    ru: ['бейгл', 'хлеб', 'круглый', 'жевательный']\r\n  },\r\n  '🍞': {\r\n    en: ['bread', 'baked', 'loaf', 'toast'],\r\n    ru: ['хлеб', 'выпеченный', 'буханка', 'тост']\r\n  },\r\n  '🥖': {\r\n    en: ['baguette', 'bread', 'French', 'long'],\r\n    ru: ['багет', 'хлеб', 'французский', 'длинный']\r\n  },\r\n  '🥨': {\r\n    en: ['pretzel', 'snack', 'salted', 'twisted'],\r\n    ru: ['претцель', 'закуска', 'солёный', 'скрученный']\r\n  },\r\n\r\n  // Prepared Foods\r\n  '🧀': {\r\n    en: ['cheese', 'dairy', 'yellow', 'savory'],\r\n    ru: ['сыр', 'молочный продукт', 'жёлтый', 'пикантный']\r\n  },\r\n  '🥚': {\r\n    en: ['egg', 'protein', 'breakfast'],\r\n    ru: ['яйцо', 'белок', 'завтрак']\r\n  },\r\n  '🍳': {\r\n    en: ['fried egg', 'breakfast', 'cooked'],\r\n    ru: ['жареное яйцо', 'завтрак', 'приготовленное']\r\n  },\r\n  '🥓': {\r\n    en: ['bacon', 'meat', 'crispy', 'breakfast'],\r\n    ru: ['бекон', 'мясо', 'хрустящий', 'завтрак']\r\n  },\r\n  '🥩': {\r\n    en: ['steak', 'meat', 'protein', 'beef'],\r\n    ru: ['стейк', 'мясо', 'белок', 'говядина']\r\n  },\r\n  '🍗': {\r\n    en: ['chicken leg', 'meat', 'drumstick', 'grilled'],\r\n    ru: ['куриная ножка', 'мясо', 'барабанная палочка', 'гриль']\r\n  },\r\n  '🍖': {\r\n    en: ['meat on bone', 'barbecue', 'protein'],\r\n    ru: ['мясо на кости', 'барбекю', 'белок']\r\n  },\r\n  '🦴': {\r\n    en: ['bone', 'meat', 'dog', 'skeleton'],\r\n    ru: ['кость', 'мясо', 'собака', 'скелет']\r\n  },\r\n  '🌭': {\r\n    en: ['hot dog', 'fast food', 'sausage'],\r\n    ru: ['хот-дог', 'фастфуд', 'колбаска']\r\n  },\r\n  '🍔': {\r\n    en: ['burger', 'fast food', 'beef', 'cheese'],\r\n    ru: ['бургер', 'фастфуд', 'говядина', 'сыр']\r\n  },\r\n  '🍟': {\r\n    en: ['french fries', 'fast food', 'crispy', 'potato'],\r\n    ru: ['картофель фри', 'фастфуд', 'хрустящий', 'картофель']\r\n  },\r\n  '🍕': {\r\n    en: ['pizza', 'cheese', 'fast food', 'Italian'],\r\n    ru: ['пицца', 'сыр', 'фастфуд', 'итальянская']\r\n  },\r\n  '🫓': {\r\n    en: ['flatbread', 'bread', 'soft'],\r\n    ru: ['лепёшка', 'хлеб', 'мягкая']\r\n  },\r\n  '🥪': {\r\n    en: ['sandwich', 'bread', 'meal'],\r\n    ru: ['бутерброд', 'хлеб', 'приём пищи']\r\n  },\r\n  '🥙': {\r\n    en: ['pita', 'bread', 'stuffed', 'Greek'],\r\n    ru: ['пита', 'хлеб', 'фаршированная', 'греческая']\r\n  },\r\n  '🧆': {\r\n    en: ['falafel', 'vegetarian', 'fried'],\r\n    ru: ['фалафель', 'вегетарианский', 'жареный']\r\n  },\r\n  '🌮': {\r\n    en: ['taco', 'Mexican', 'spicy'],\r\n    ru: ['тако', 'мексиканская', 'острая']\r\n  },\r\n  '🌯': {\r\n    en: ['burrito', 'Mexican', 'stuffed'],\r\n    ru: ['буррито', 'мексиканская', 'фаршированная']\r\n  },\r\n  '🫔': {\r\n    en: ['tamale', 'Mexican', 'corn'],\r\n    ru: ['тамале', 'мексиканская', 'кукурузная']\r\n  },\r\n  '🥗': {\r\n    en: ['salad', 'healthy', 'vegetable'],\r\n    ru: ['салат', 'здоровый', 'овощной']\r\n  },\r\n\r\n  // Asian Foods\r\n  '🥘': {\r\n    en: ['paella', 'stew', 'seafood'],\r\n    ru: ['паэлья', 'тушёное блюдо', 'морепродукты']\r\n  },\r\n  '🫕': {\r\n    en: ['fondue', 'melted', 'cheese'],\r\n    ru: ['фондю', 'расплавленный', 'сыр']\r\n  },\r\n  '🥫': {\r\n    en: ['canned food', 'storage', 'preserved'],\r\n    ru: ['консервы', 'хранение', 'сохранённое']\r\n  },\r\n  '🍝': {\r\n    en: ['spaghetti', 'pasta', 'Italian'],\r\n    ru: ['спагетти', 'паста', 'итальянская']\r\n  },\r\n  '🍜': {\r\n    en: ['ramen', 'noodles', 'Asian'],\r\n    ru: ['рамен', 'лапша', 'азиатская']\r\n  },\r\n  '🍲': {\r\n    en: ['hotpot', 'stew', 'broth'],\r\n    ru: ['хотпот', 'тушёное блюдо', 'бульон']\r\n  },\r\n  '🍛': {\r\n    en: ['curry', 'spicy', 'rice'],\r\n    ru: ['карри', 'острая', 'рис']\r\n  },\r\n  '🍣': {\r\n    en: ['sushi', 'Japanese', 'fish'],\r\n    ru: ['суши', 'японская', 'рыба']\r\n  },\r\n  '🍱': {\r\n    en: ['bento box', 'Japanese', 'meal'],\r\n    ru: ['бенто', 'японская', 'еда']\r\n  },\r\n  '🥟': {\r\n    en: ['dumpling', 'Asian', 'stuffed'],\r\n    ru: ['пельмени', 'азиатские', 'фаршированные']\r\n  },\r\n  '🦪': {\r\n    en: ['oyster', 'seafood', 'shellfish'],\r\n    ru: ['устрица', 'морепродукт', 'раковина']\r\n  },\r\n  '🍤': {\r\n    en: ['shrimp tempura', 'fried', 'seafood'],\r\n    ru: ['креветка темпура', 'жареная', 'морепродукт']\r\n  },\r\n  '🍙': {\r\n    en: ['rice ball', 'Japanese', 'onigiri'],\r\n    ru: ['рисовый шар', 'японская', 'онигири']\r\n  },\r\n  '🍚': {\r\n    en: ['cooked rice', 'staple', 'Asian'],\r\n    ru: ['варёный рис', 'основа', 'азиатская']\r\n  },\r\n  '🍘': {\r\n    en: ['rice cracker', 'snack', 'Japanese'],\r\n    ru: ['рисовый крекер', 'закуска', 'японская']\r\n  },\r\n  '🍥': {\r\n    en: ['fish cake', 'Japanese', 'swirl'],\r\n    ru: ['рыбный пирожок', 'японская', 'спираль']\r\n  },\r\n  '🥠': {\r\n    en: ['fortune cookie', 'Chinese', 'paper'],\r\n    ru: ['печенье с предсказанием', 'китайское', 'бумажное']\r\n  },\r\n  '🥮': {\r\n    en: ['mooncake', 'Chinese', 'festival'],\r\n    ru: ['лунное печенье', 'китайское', 'фестиваль']\r\n  },\r\n\r\n  // Sweets & Desserts\r\n  '🍢': {\r\n    en: ['skewered snack', 'street food'],\r\n    ru: ['шампур', 'уличная еда']\r\n  },\r\n  '🍡': {\r\n    en: ['dango', 'Japanese', 'mochi'],\r\n    ru: ['данго', 'японское', 'мочи']\r\n  },\r\n  '🍧': {\r\n    en: ['shaved ice', 'dessert', 'cold'],\r\n    ru: ['измельчённый лёд', 'десерт', 'холодный']\r\n  },\r\n  '🍨': {\r\n    en: ['ice cream', 'cold', 'sweet'],\r\n    ru: ['мороженое', 'холодное', 'сладкое']\r\n  },\r\n  '🍦': {\r\n    en: ['soft serve', 'dessert', 'cold'],\r\n    ru: ['мягкое мороженое', 'десерт', 'холодное']\r\n  },\r\n  '🥧': {\r\n    en: ['pie', 'dessert', 'baked'],\r\n    ru: ['пирог', 'десерт', 'выпеченный']\r\n  },\r\n  '🧁': {\r\n    en: ['cupcake', 'sweet', 'frosting'],\r\n    ru: ['капкейк', 'сладкий', 'с глазурью']\r\n  },\r\n  '🍰': {\r\n    en: ['cake', 'dessert', 'slice'],\r\n    ru: ['торт', 'десерт', 'кусок']\r\n  },\r\n  '🎂': {\r\n    en: ['birthday cake', 'celebration', 'sweet'],\r\n    ru: ['торт ко дню рождения', 'празднование', 'сладкий']\r\n  },\r\n  '🍮': {\r\n    en: ['flan', 'custard', 'dessert'],\r\n    ru: ['флан', 'кастард', 'десерт']\r\n  },\r\n  '🍭': {\r\n    en: ['lollipop', 'candy', 'sweet'],\r\n    ru: ['леденец', 'конфета', 'сладкий']\r\n  },\r\n  '🍬': {\r\n    en: ['candy', 'sweet', 'sugar'],\r\n    ru: ['конфета', 'сладость', 'сахар']\r\n  },\r\n  '🍫': {\r\n    en: ['chocolate', 'sweet', 'cocoa'],\r\n    ru: ['шоколад', 'сладкий', 'какао']\r\n  },\r\n  '🍿': {\r\n    en: ['popcorn', 'snack', 'buttery'],\r\n    ru: ['попкорн', 'закуска', 'масляный']\r\n  },\r\n  '🍩': {\r\n    en: ['doughnut', 'sweet', 'fried'],\r\n    ru: ['пончик', 'сладкий', 'жареный']\r\n  },\r\n  '🍪': {\r\n    en: ['cookie', 'sweet', 'baked'],\r\n    ru: ['печенье', 'сладкое', 'выпеченное']\r\n  },\r\n\r\n  // Drinks\r\n  '🫖': {\r\n    en: ['teapot', 'tea', 'hot'],\r\n    ru: ['чайник', 'чай', 'горячий']\r\n  },\r\n  '☕': {\r\n    en: ['coffee', 'hot drink', 'caffeine'],\r\n    ru: ['кофе', 'горячий напиток', 'кофеин']\r\n  },\r\n  '🍵': {\r\n    en: ['green tea', 'hot', 'Japanese'],\r\n    ru: ['зелёный чай', 'горячий', 'японский']\r\n  },\r\n  '🧃': {\r\n    en: ['juice', 'drink', 'fruit'],\r\n    ru: ['сок', 'напиток', 'фруктовый']\r\n  },\r\n  '🥤': {\r\n    en: ['soft drink', 'straw', 'fast food'],\r\n    ru: ['безалкогольный напиток', 'с трубочкой', 'фастфуд']\r\n  },\r\n  '🧋': {\r\n    en: ['bubble tea', 'milk tea', 'boba'],\r\n    ru: ['бабл-ти', 'молочный чай', 'боба']\r\n  },\r\n  '🍶': {\r\n    en: ['sake', 'Japanese', 'rice wine'],\r\n    ru: ['саке', 'японский', 'рисовое вино']\r\n  },\r\n  '🍺': {\r\n    en: ['beer', 'drink', 'alcohol'],\r\n    ru: ['пиво', 'напиток', 'алкоголь']\r\n  },\r\n  '🍻': {\r\n    en: ['cheers', 'beer', 'drinking'],\r\n    ru: ['ура', 'пиво', 'выпивка']\r\n  },\r\n  '🥂': {\r\n    en: ['champagne', 'celebration', 'toast'],\r\n    ru: ['шампанское', 'празднование', 'тост']\r\n  },\r\n  '🍷': {\r\n    en: ['wine', 'drink', 'red'],\r\n    ru: ['вино', 'напиток', 'красное']\r\n  },\r\n  '🥃': {\r\n    en: ['whiskey', 'liquor', 'alcohol'],\r\n    ru: ['виски', 'ликёр', 'алкоголь']\r\n  },\r\n  '🍸': {\r\n    en: ['cocktail', 'martini', 'drink'],\r\n    ru: ['коктейль', 'мартини', 'напиток']\r\n  },\r\n  '🍹': {\r\n    en: ['tropical drink', 'cocktail', 'summer'],\r\n    ru: ['тропический напиток', 'коктейль', 'лето']\r\n  },\r\n  '🧉': {\r\n    en: ['mate', 'South American', 'tea'],\r\n    ru: ['мате', 'южноамериканский', 'чай']\r\n  },\r\n  '🍾': {\r\n    en: ['champagne bottle', 'celebration', 'party'],\r\n    ru: ['бутылка шампанского', 'празднование', 'вечеринка']\r\n  },\r\n\r\n  //------------------------- Activities & Objects -------------------------\r\n  // Sports\r\n  '⚽': {\r\n    en: ['soccer', 'football', 'sports', 'ball'],\r\n    ru: ['футбол', 'футбол', 'спорт', 'мяч']\r\n  },\r\n  '🏀': {\r\n    en: ['basketball', 'sports', 'hoop', 'dunk'],\r\n    ru: ['баскетбол', 'спорт', 'кольцо', 'данк']\r\n  },\r\n  '🏈': {\r\n    en: ['American football', 'sports', 'rugby'],\r\n    ru: ['американский футбол', 'спорт', 'регби']\r\n  },\r\n  '⚾': {\r\n    en: ['baseball', 'sports', 'bat'],\r\n    ru: ['бейсбол', 'спорт', 'бита']\r\n  },\r\n  '🥎': {\r\n    en: ['softball', 'sports', 'ball'],\r\n    ru: ['софтбол', 'спорт', 'мяч']\r\n  },\r\n  '🎾': {\r\n    en: ['tennis', 'sports', 'racket'],\r\n    ru: ['теннис', 'спорт', 'ракетка']\r\n  },\r\n  '🏐': {\r\n    en: ['volleyball', 'sports', 'beach'],\r\n    ru: ['волейбол', 'спорт', 'пляж']\r\n  },\r\n  '🏉': {\r\n    en: ['rugby', 'sports', 'oval ball'],\r\n    ru: ['регби', 'спорт', 'овальный мяч']\r\n  },\r\n  '🥏': {\r\n    en: ['frisbee', 'throw', 'flying disc'],\r\n    ru: ['фрисби', 'бросок', 'летающий диск']\r\n  },\r\n  '🎱': {\r\n    en: ['billiards', '8 ball', 'pool'],\r\n    ru: ['бильярд', 'восьмёрка', 'пул']\r\n  },\r\n  '🪀': {\r\n    en: ['yo-yo', 'toy', 'string'],\r\n    ru: ['йо-йо', 'игрушка', 'верёвка']\r\n  },\r\n  '🏓': {\r\n    en: ['ping pong', 'table tennis', 'sports'],\r\n    ru: ['пинг-понг', 'настольный теннис', 'спорт']\r\n  },\r\n  '🏸': {\r\n    en: ['badminton', 'racket', 'sports'],\r\n    ru: ['бадминтон', 'ракетка', 'спорт']\r\n  },\r\n  '🏒': {\r\n    en: ['hockey', 'ice hockey', 'sports'],\r\n    ru: ['хоккей', 'хоккей на льду', 'спорт']\r\n  },\r\n  '🏑': {\r\n    en: ['field hockey', 'sports', 'stick'],\r\n    ru: ['хоккей на траве', 'спорт', 'клюшка']\r\n  },\r\n  '🥍': {\r\n    en: ['lacrosse', 'sports', 'net'],\r\n    ru: ['лакросс', 'спорт', 'сеть']\r\n  },\r\n  '🏏': {\r\n    en: ['cricket', 'bat', 'sports'],\r\n    ru: ['крикет', 'бита', 'спорт']\r\n  },\r\n  '⛳': {\r\n    en: ['golf', 'sports', 'hole in one'],\r\n    ru: ['гольф', 'спорт', 'луночка']\r\n  },\r\n  '🪁': {\r\n    en: ['kite', 'flying', 'wind'],\r\n    ru: ['змей', 'летать', 'ветер']\r\n  },\r\n  '🎣': {\r\n    en: ['fishing', 'hook', 'water'],\r\n    ru: ['рыбалка', 'крючок', 'вода']\r\n  },\r\n  '🤿': {\r\n    en: ['diving', 'underwater', 'snorkel'],\r\n    ru: ['дайвинг', 'под водой', 'снорклинг']\r\n  },\r\n  '🎽': {\r\n    en: ['running', 'jersey', 'athlete'],\r\n    ru: ['бег', 'майка', 'спортсмен']\r\n  },\r\n  '🛹': {\r\n    en: ['skateboard', 'sports', 'extreme'],\r\n    ru: ['скейтборд', 'спорт', 'экстрим']\r\n  },\r\n  '🛼': {\r\n    en: ['roller skate', 'sports', 'wheels'],\r\n    ru: ['роликовые коньки', 'спорт', 'колёса']\r\n  },\r\n  '🛷': {\r\n    en: ['sled', 'winter', 'snow'],\r\n    ru: ['санки', 'зима', 'снег']\r\n  },\r\n  '⛸️': {\r\n    en: ['ice skate', 'winter', 'sports'],\r\n    ru: ['коньки', 'зима', 'спорт']\r\n  },\r\n  '🥌': {\r\n    en: ['curling', 'winter', 'stone'],\r\n    ru: ['керлинг', 'зима', 'камень']\r\n  },\r\n  '⛷️': {\r\n    en: ['skiing', 'winter', 'snow'],\r\n    ru: ['лыжи', 'зима', 'снег']\r\n  },\r\n  '🏂': {\r\n    en: ['snowboarding', 'sports', 'snow'],\r\n    ru: ['сноуборд', 'спорт', 'снег']\r\n  },\r\n  '🪂': {\r\n    en: ['parachute', 'skydiving', 'air'],\r\n    ru: ['парашют', 'скайдайвинг', 'воздух']\r\n  },\r\n  '🏋️': {\r\n    en: ['weightlifting', 'gym', 'strong'],\r\n    ru: ['тяжёлая атлетика', 'спортзал', 'сильный']\r\n  },\r\n  '🤼': {\r\n    en: ['wrestling', 'fight', 'grapple'],\r\n    ru: ['борьба', 'драка', 'схватка']\r\n  },\r\n  '🤸': {\r\n    en: ['gymnastics', 'acrobatics', 'flip'],\r\n    ru: ['гимнастика', 'акробатика', 'сальто']\r\n  },\r\n  '⛹️': {\r\n    en: ['basketball', 'dribbling', 'sports'],\r\n    ru: ['баскетбол', 'дриблинг', 'спорт']\r\n  },\r\n  '🤾': {\r\n    en: ['handball', 'sports', 'throw'],\r\n    ru: ['гандбол', 'спорт', 'бросок']\r\n  },\r\n  '🏌️': {\r\n    en: ['golf', 'swing', 'sports'],\r\n    ru: ['гольф', 'мах', 'спорт']\r\n  },\r\n  '🏇': {\r\n    en: ['horse racing', 'sports', 'jockey'],\r\n    ru: ['конные скачки', 'спорт', 'жокей']\r\n  },\r\n  '🧘': {\r\n    en: ['meditation', 'yoga', 'zen'],\r\n    ru: ['медитация', 'йога', 'дзен']\r\n  },\r\n  '🏄': {\r\n    en: ['surfing', 'wave', 'water'],\r\n    ru: ['серфинг', 'волна', 'вода']\r\n  },\r\n  '🏊': {\r\n    en: ['swimming', 'water', 'pool'],\r\n    ru: ['плавание', 'вода', 'бассейн']\r\n  },\r\n\r\n  // Activities (cultural/entertainment)\r\n  '🤽': {\r\n    en: ['water polo', 'sports', 'swimming'],\r\n    ru: ['водное поло', 'спорт', 'плавание']\r\n  },\r\n  '🚣': {\r\n    en: ['rowing', 'boat', 'water'],\r\n    ru: ['гребля', 'лодка', 'вода']\r\n  },\r\n  '🧗': {\r\n    en: ['rock climbing', 'sports', 'mountain'],\r\n    ru: ['скалолазание', 'спорт', 'гора']\r\n  },\r\n  '🚴': {\r\n    en: ['cycling', 'bike', 'sports'],\r\n    ru: ['велоспорт', 'велосипед', 'спорт']\r\n  },\r\n  '🚵': {\r\n    en: ['mountain biking', 'sports', 'outdoor'],\r\n    ru: ['маунтинбайк', 'спорт', 'на свежем воздухе']\r\n  },\r\n  '🎪': {\r\n    en: ['circus', 'tent', 'performance'],\r\n    ru: ['цирк', 'палатка', 'выступление']\r\n  },\r\n  '🎭': {\r\n    en: ['theater', 'drama', 'acting'],\r\n    ru: ['театр', 'драма', 'актерство']\r\n  },\r\n  '🎨': {\r\n    en: ['painting', 'art', 'colors'],\r\n    ru: ['живопись', 'искусство', 'цвета']\r\n  },\r\n  '🎬': {\r\n    en: ['film', 'clapperboard', 'movie'],\r\n    ru: ['фильм', 'хлопушка', 'кино']\r\n  },\r\n  '🎤': {\r\n    en: ['microphone', 'singing', 'music'],\r\n    ru: ['микрофон', 'пение', 'музыка']\r\n  },\r\n  '🎧': {\r\n    en: ['headphones', 'music', 'listening'],\r\n    ru: ['наушники', 'музыка', 'прослушивание']\r\n  },\r\n  '🎼': {\r\n    en: ['music', 'sheet', 'notes'],\r\n    ru: ['музыка', 'ноты', 'сценарий']\r\n  },\r\n  '🎹': {\r\n    en: ['piano', 'keyboard', 'music'],\r\n    ru: ['пианино', 'клавиатура', 'музыка']\r\n  },\r\n  '🥁': {\r\n    en: ['drums', 'music', 'percussion'],\r\n    ru: ['барабаны', 'музыка', 'ударные']\r\n  },\r\n  '🎷': {\r\n    en: ['saxophone', 'jazz', 'music'],\r\n    ru: ['саксофон', 'джаз', 'музыка']\r\n  },\r\n  '🎺': {\r\n    en: ['trumpet', 'brass', 'music'],\r\n    ru: ['труба', 'латунь', 'музыка']\r\n  },\r\n  '🎸': {\r\n    en: ['guitar', 'music', 'rock'],\r\n    ru: ['гитара', 'музыка', 'рок']\r\n  },\r\n  '🎻': {\r\n    en: ['violin', 'music', 'strings'],\r\n    ru: ['скрипка', 'музыка', 'струны']\r\n  },\r\n  '🎲': {\r\n    en: ['dice', 'game', 'board game'],\r\n    ru: ['кубики', 'игра', 'настольная игра']\r\n  },\r\n  '🎯': {\r\n    en: ['dart', 'target', 'game'],\r\n    ru: ['дротик', 'цель', 'игра']\r\n  },\r\n  '🎳': {\r\n    en: ['bowling', 'sports', 'pins'],\r\n    ru: ['боулинг', 'спорт', 'кегли']\r\n  },\r\n  '🎮': {\r\n    en: ['video game', 'console', 'gaming'],\r\n    ru: ['видеоигра', 'консоль', 'игры']\r\n  },\r\n  '🎰': {\r\n    en: ['slot machine', 'casino', 'gambling'],\r\n    ru: ['игровой автомат', 'казино', 'азартные игры']\r\n  },\r\n  '🧩': {\r\n    en: ['puzzle', 'pieces', 'brain teaser'],\r\n    ru: ['головоломка', 'кусочки', 'разминка для мозга']\r\n  },\r\n  '🎫': {\r\n    en: ['ticket', 'event', 'entry'],\r\n    ru: ['билет', 'событие', 'вход']\r\n  },\r\n  '🎟️': {\r\n    en: ['admission', 'ticket', 'event'],\r\n    ru: ['входной билет', 'билет', 'событие']\r\n  },\r\n\r\n  //------------------------- Travel & Places -------------------------\r\n  // Land Transport\r\n  '🚗': {\r\n    en: ['car', 'automobile', 'vehicle', 'transport'],\r\n    ru: ['автомобиль', 'машина', 'транспортное средство', 'транспорт']\r\n  },\r\n  '🚕': {\r\n    en: ['taxi', 'cab', 'transport', 'vehicle'],\r\n    ru: ['такси', 'маршрутка', 'транспорт', 'транспортное средство']\r\n  },\r\n  '🚙': {\r\n    en: ['suv', 'vehicle', 'transport'],\r\n    ru: ['внедорожник', 'транспортное средство', 'транспорт']\r\n  },\r\n  '🚌': {\r\n    en: ['bus', 'public transport', 'commute'],\r\n    ru: ['автобус', 'общественный транспорт', 'коммьют']\r\n  },\r\n  '🚎': {\r\n    en: ['trolleybus', 'public transport', 'electric'],\r\n    ru: ['троллейбус', 'общественный транспорт', 'электрический']\r\n  },\r\n  '🏎️': {\r\n    en: ['race car', 'sports car', 'fast', 'speed'],\r\n    ru: ['гоночный автомобиль', 'спортивный автомобиль', 'быстрый', 'скорость']\r\n  },\r\n  '🚓': {\r\n    en: ['police car', 'law enforcement', 'vehicle'],\r\n    ru: ['полицейская машина', 'правоохранительные органы', 'транспортное средство']\r\n  },\r\n  '🚑': {\r\n    en: ['ambulance', 'emergency', 'hospital'],\r\n    ru: ['скорая помощь', 'чрезвычайная ситуация', 'больница']\r\n  },\r\n  '🚒': {\r\n    en: ['fire truck', 'firefighter', 'emergency'],\r\n    ru: ['пожарная машина', 'пожарный', 'чрезвычайная ситуация']\r\n  },\r\n  '🚐': {\r\n    en: ['van', 'minibus', 'transport'],\r\n    ru: ['фургон', 'маршрутка', 'транспорт']\r\n  },\r\n  '🛻': {\r\n    en: ['pickup truck', 'off-road', 'vehicle'],\r\n    ru: ['пикап', 'для бездорожья', 'транспортное средство']\r\n  },\r\n  '🚚': {\r\n    en: ['delivery truck', 'freight', 'cargo'],\r\n    ru: ['грузовик', 'доставка', 'груз']\r\n  },\r\n  '🚛': {\r\n    en: ['truck', 'semi-trailer', 'transport'],\r\n    ru: ['грузовик', 'самосвал', 'транспорт']\r\n  },\r\n  '🚜': {\r\n    en: ['tractor', 'farming', 'agriculture'],\r\n    ru: ['трактор', 'сельское хозяйство', 'агрономия']\r\n  },\r\n  '🛵': {\r\n    en: ['scooter', 'motorbike', 'moped'],\r\n    ru: ['скутер', 'мотоцикл', 'мопед']\r\n  },\r\n  '🏍️': {\r\n    en: ['motorcycle', 'bike', 'racing'],\r\n    ru: ['мотоцикл', 'байк', 'гоночный']\r\n  },\r\n  '🛺': {\r\n    en: ['rickshaw', 'auto rickshaw', 'transport'],\r\n    ru: ['рикша', 'авто-рикша', 'транспорт']\r\n  },\r\n  '🚲': {\r\n    en: ['bicycle', 'bike', 'pedal', 'cycling'],\r\n    ru: ['велосипед', 'байк', 'педаль', 'велоспорт']\r\n  },\r\n  '🛴': {\r\n    en: ['kick scooter', 'scooter', 'ride'],\r\n    ru: ['самокат', 'скутер', 'поездка']\r\n  },\r\n\r\n  // Air Transport\r\n  '✈️': {\r\n    en: ['airplane', 'flight', 'travel', 'sky'],\r\n    ru: ['самолёт', 'рейс', 'путешествие', 'небо']\r\n  },\r\n  '🛩️': {\r\n    en: ['small airplane', 'aviation', 'sky'],\r\n    ru: ['малый самолёт', 'авиация', 'небо']\r\n  },\r\n  '🛫': {\r\n    en: ['departure', 'takeoff', 'airport'],\r\n    ru: ['вылет', 'взлёт', 'аэропорт']\r\n  },\r\n  '🛬': {\r\n    en: ['landing', 'arrival', 'airport'],\r\n    ru: ['посадка', 'прибытие', 'аэропорт']\r\n  },\r\n  '🚁': {\r\n    en: ['helicopter', 'aviation', 'air'],\r\n    ru: ['вертолёт', 'авиация', 'воздух']\r\n  },\r\n  '🚀': {\r\n    en: ['rocket', 'space', 'launch', 'NASA'],\r\n    ru: ['ракета', 'космос', 'запуск', 'НАСА']\r\n  },\r\n  '🛸': {\r\n    en: ['UFO', 'alien', 'spaceship', 'extraterrestrial'],\r\n    ru: ['НЛО', 'инопланетянин', 'космический корабль', 'внеземной']\r\n  },\r\n\r\n  // Water Transport\r\n  '🛶': {\r\n    en: ['canoe', 'boat', 'paddle', 'water'],\r\n    ru: ['каноэ', 'лодка', 'весло', 'вода']\r\n  },\r\n  '⛵': {\r\n    en: ['sailboat', 'yacht', 'sea'],\r\n    ru: ['парусник', 'яхта', 'море']\r\n  },\r\n  '🚤': {\r\n    en: ['motorboat', 'speedboat', 'ocean'],\r\n    ru: ['моторная лодка', 'скоростной катер', 'океан']\r\n  },\r\n  '🛥️': {\r\n    en: ['yacht', 'luxury', 'boat'],\r\n    ru: ['яхта', 'роскошь', 'лодка']\r\n  },\r\n  '🛳️': {\r\n    en: ['cruise ship', 'ocean', 'voyage'],\r\n    ru: ['круизный лайнер', 'океан', 'путешествие']\r\n  },\r\n  '⛴️': {\r\n    en: ['ferry', 'boat', 'transport'],\r\n    ru: ['паром', 'лодка', 'транспорт']\r\n  },\r\n  '🚢': {\r\n    en: ['ship', 'ocean', 'voyage'],\r\n    ru: ['корабль', 'океан', 'путешествие']\r\n  },\r\n\r\n  // Places\r\n  '🏰': {\r\n    en: ['castle', 'fortress', 'history'],\r\n    ru: ['замок', 'крепость', 'история']\r\n  },\r\n  '🏯': {\r\n    en: ['Japanese castle', 'Asia', 'samurai'],\r\n    ru: ['японский замок', 'Азия', 'самурай']\r\n  },\r\n  '🏟️': {\r\n    en: ['stadium', 'sports', 'arena'],\r\n    ru: ['стадион', 'спорт', 'арена']\r\n  },\r\n  '🏖️': {\r\n    en: ['beach', 'sun', 'vacation'],\r\n    ru: ['пляж', 'солнце', 'отпуск']\r\n  },\r\n  '🏝️': {\r\n    en: ['island', 'tropical', 'vacation'],\r\n    ru: ['остров', 'тропический', 'отпуск']\r\n  },\r\n  '🏜️': {\r\n    en: ['desert', 'sand', 'dry'],\r\n    ru: ['пустыня', 'песок', 'сухой']\r\n  },\r\n  '🌋': {\r\n    en: ['volcano', 'eruption', 'lava'],\r\n    ru: ['вулкан', 'извержение', 'лава']\r\n  },\r\n  '⛰️': {\r\n    en: ['mountain', 'hiking', 'nature'],\r\n    ru: ['гора', 'поход', 'природа']\r\n  },\r\n  '🏔️': {\r\n    en: ['snowy mountain', 'climbing', 'cold'],\r\n    ru: ['снежная гора', 'скалолазание', 'холод']\r\n  },\r\n  '🗻': {\r\n    en: ['Mount Fuji', 'Japan', 'scenic'],\r\n    ru: ['гора Фудзи', 'Япония', 'живописный']\r\n  },\r\n  '🏕️': {\r\n    en: ['camping', 'tent', 'outdoors'],\r\n    ru: ['кемпинг', 'палатка', 'на природе']\r\n  },\r\n  '🏭': {\r\n    en: ['factory', 'industry', 'building'],\r\n    ru: ['фабрика', 'индустрия', 'здание']\r\n  },\r\n  '🏢': {\r\n    en: ['office building', 'corporate', 'city'],\r\n    ru: ['офисное здание', 'корпоративный', 'город']\r\n  },\r\n  '🏬': {\r\n    en: ['shopping mall', 'retail', 'stores'],\r\n    ru: ['торговый центр', 'розничная торговля', 'магазины']\r\n  },\r\n  '🏣': {\r\n    en: ['post office', 'mail', 'building'],\r\n    ru: ['почтовое отделение', 'почта', 'здание']\r\n  },\r\n  '🏤': {\r\n    en: ['post office', 'mail', 'postal service'],\r\n    ru: ['почтовое отделение', 'почта', 'почтовая служба']\r\n  },\r\n  '🏥': {\r\n    en: ['hospital', 'healthcare', 'emergency'],\r\n    ru: ['больница', 'здравоохранение', 'чрезвычайная ситуация']\r\n  },\r\n  '🏦': {\r\n    en: ['bank', 'money', 'finance'],\r\n    ru: ['банк', 'деньги', 'финансы']\r\n  },\r\n  '🏨': {\r\n    en: ['hotel', 'accommodation', 'stay'],\r\n    ru: ['отель', 'размещение', 'проживание']\r\n  },\r\n  '🏪': {\r\n    en: ['convenience store', 'shopping', '24/7'],\r\n    ru: ['магазин', 'шоппинг', 'круглосуточно']\r\n  },\r\n  '🏫': {\r\n    en: ['school', 'education', 'learning'],\r\n    ru: ['школа', 'образование', 'обучение']\r\n  },\r\n  '🏩': {\r\n    en: ['love hotel', 'romantic', 'Japan'],\r\n    ru: ['любовный отель', 'романтический', 'Япония']\r\n  },\r\n  '💒': {\r\n    en: ['wedding', 'church', 'marriage'],\r\n    ru: ['свадьба', 'церковь', 'брак']\r\n  },\r\n  '⛪': {\r\n    en: ['church', 'Christianity', 'religion'],\r\n    ru: ['церковь', 'христианство', 'религия']\r\n  },\r\n  '🕌': {\r\n    en: ['mosque', 'Islam', 'prayer'],\r\n    ru: ['мечеть', 'ислам', 'молитва']\r\n  },\r\n  '🕍': {\r\n    en: ['synagogue', 'Judaism', 'religion'],\r\n    ru: ['синагога', 'иудаизм', 'религия']\r\n  },\r\n  '🛕': {\r\n    en: ['hindu temple', 'spiritual', 'India'],\r\n    ru: ['индуистский храм', 'духовный', 'Индия']\r\n  },\r\n  '⛩️': {\r\n    en: ['shrine', 'torii', 'Japan'],\r\n    ru: ['святилище', 'тории', 'Япония']\r\n  },\r\n  '🏛️': {\r\n    en: ['government building', 'politics', 'history'],\r\n    ru: ['правительственное здание', 'политика', 'история']\r\n  },\r\n\r\n  //------------------------- Objects & Symbols -------------------------\r\n  // Tools & Technology\r\n  '📱': {\r\n    en: ['smartphone', 'mobile', 'phone'],\r\n    ru: ['смартфон', 'мобильный', 'телефон']\r\n  },\r\n  '💻': {\r\n    en: ['laptop', 'computer', 'technology'],\r\n    ru: ['ноутбук', 'компьютер', 'технология']\r\n  },\r\n  '⌨️': {\r\n    en: ['keyboard', 'typing', 'computer'],\r\n    ru: ['клавиатура', 'набор текста', 'компьютер']\r\n  },\r\n  '🖥️': {\r\n    en: ['desktop', 'monitor', 'screen'],\r\n    ru: ['настольный компьютер', 'монитор', 'экран']\r\n  },\r\n  '🖨️': {\r\n    en: ['printer', 'print', 'office'],\r\n    ru: ['принтер', 'печать', 'офис']\r\n  },\r\n  '🖱️': {\r\n    en: ['mouse', 'computer', 'click'],\r\n    ru: ['мышь', 'компьютер', 'клик']\r\n  },\r\n  '🖲️': {\r\n    en: ['trackball', 'navigation', 'input'],\r\n    ru: ['трекбол', 'навигация', 'ввод']\r\n  },\r\n  '🕹️': {\r\n    en: ['joystick', 'gaming', 'console'],\r\n    ru: ['джойстик', 'игры', 'консоль']\r\n  },\r\n  '🗜️': {\r\n    en: ['clamp', 'tool', 'hardware'],\r\n    ru: ['зажим', 'инструмент', 'аппаратное обеспечение']\r\n  },\r\n  '💽': {\r\n    en: ['mini disc', 'storage', 'media'],\r\n    ru: ['мини-диск', 'хранение', 'медиа']\r\n  },\r\n  '💾': {\r\n    en: ['floppy disk', 'save', 'data'],\r\n    ru: ['флоппи-диск', 'сохранить', 'данные']\r\n  },\r\n  '💿': {\r\n    en: ['CD', 'compact disc', 'music'],\r\n    ru: ['CD', 'компакт-диск', 'музыка']\r\n  },\r\n  '📀': {\r\n    en: ['DVD', 'video', 'disc'],\r\n    ru: ['DVD', 'видео', 'диск']\r\n  },\r\n  '📼': {\r\n    en: ['VHS', 'video cassette', 'retro'],\r\n    ru: ['VHS', 'видеокассета', 'ретро']\r\n  },\r\n  '📷': {\r\n    en: ['camera', 'photo', 'photography'],\r\n    ru: ['камера', 'фото', 'фотография']\r\n  },\r\n  '📸': {\r\n    en: ['camera flash', 'photography', 'snapshot'],\r\n    ru: ['вспышка камеры', 'фотография', 'снимок']\r\n  },\r\n  '📹': {\r\n    en: ['video camera', 'recording', 'film'],\r\n    ru: ['видеокамера', 'запись', 'фильм']\r\n  },\r\n  '🎥': {\r\n    en: ['movie camera', 'film', 'cinema'],\r\n    ru: ['кино-камера', 'фильм', 'кино']\r\n  },\r\n\r\n  // Office & Communication\r\n  '📞': {\r\n    en: ['telephone', 'call', 'communication'],\r\n    ru: ['телефон', 'звонок', 'коммуникация']\r\n  },\r\n  '☎️': {\r\n    en: ['phone', 'landline', 'call'],\r\n    ru: ['телефон', 'стационарный', 'звонок']\r\n  },\r\n  '📟': {\r\n    en: ['pager', 'message', 'communication'],\r\n    ru: ['пейджер', 'сообщение', 'коммуникация']\r\n  },\r\n  '📠': {\r\n    en: ['fax machine', 'office', 'document'],\r\n    ru: ['факс', 'офис', 'документ']\r\n  },\r\n  '📺': {\r\n    en: ['TV', 'television', 'screen'],\r\n    ru: ['телевизор', 'теле', 'экран']\r\n  },\r\n  '📻': {\r\n    en: ['radio', 'broadcast', 'audio'],\r\n    ru: ['радио', 'трансляция', 'аудио']\r\n  },\r\n  '🎙️': {\r\n    en: ['microphone', 'recording', 'podcast'],\r\n    ru: ['микрофон', 'запись', 'подкаст']\r\n  },\r\n  '🎚️': {\r\n    en: ['control knob', 'audio', 'volume'],\r\n    ru: ['ручка управления', 'аудио', 'громкость']\r\n  },\r\n  '🎛️': {\r\n    en: ['sliders', 'equalizer', 'settings'],\r\n    ru: ['ползунки', 'эквалайзер', 'настройки']\r\n  },\r\n  '📡': {\r\n    en: ['satellite dish', 'broadcast', 'signal'],\r\n    ru: ['спутниковая антенна', 'трансляция', 'сигнал']\r\n  },\r\n  '🔋': {\r\n    en: ['battery', 'power', 'energy'],\r\n    ru: ['батарея', 'мощность', 'энергия']\r\n  },\r\n  '🔌': {\r\n    en: ['plug', 'electricity', 'charging'],\r\n    ru: ['штекер', 'электричество', 'зарядка']\r\n  },\r\n  '💡': {\r\n    en: ['light bulb', 'idea', 'illumination'],\r\n    ru: ['лампочка', 'идея', 'освещение']\r\n  },\r\n  '🔦': {\r\n    en: ['flashlight', 'torch', 'light'],\r\n    ru: ['фонарик', 'фонарь', 'свет']\r\n  },\r\n  '🕯️': {\r\n    en: ['candle', 'light', 'fire'],\r\n    ru: ['свеча', 'свет', 'огонь']\r\n  },\r\n\r\n  // Household & Money\r\n  '🧯': {\r\n    en: ['fire extinguisher', 'safety', 'fire'],\r\n    ru: ['огнетушитель', 'безопасность', 'огонь']\r\n  },\r\n  '🛢️': {\r\n    en: ['barrel', 'oil', 'fuel'],\r\n    ru: ['бочка', 'масло', 'топливо']\r\n  },\r\n  '💸': {\r\n    en: ['money with wings', 'cash', 'spending'],\r\n    ru: ['деньги с крыльями', 'наличные', 'расходы']\r\n  },\r\n  '💵': {\r\n    en: ['dollar bills', 'money', 'USD'],\r\n    ru: ['долларовые банкноты', 'деньги', 'USD']\r\n  },\r\n  '💴': {\r\n    en: ['yen', 'money', 'Japan'],\r\n    ru: ['иена', 'деньги', 'Япония']\r\n  },\r\n  '💶': {\r\n    en: ['euro', 'money', 'currency'],\r\n    ru: ['евро', 'деньги', 'валюта']\r\n  },\r\n  '💷': {\r\n    en: ['pound', 'money', 'UK'],\r\n    ru: ['фунт', 'деньги', 'Великобритания']\r\n  },\r\n  '🪙': {\r\n    en: ['coin', 'currency', 'money'],\r\n    ru: ['монета', 'валюта', 'деньги']\r\n  },\r\n  '💰': {\r\n    en: ['money bag', 'wealth', 'rich'],\r\n    ru: ['мешок с деньгами', 'богатство', 'богатый']\r\n  },\r\n  '💳': {\r\n    en: ['credit card', 'banking', 'payment'],\r\n    ru: ['кредитная карта', 'банковское дело', 'оплата']\r\n  },\r\n  '💎': {\r\n    en: ['gem', 'diamond', 'valuable'],\r\n    ru: ['драгоценный камень', 'бриллиант', 'ценный']\r\n  },\r\n  '⚖️': {\r\n    en: ['balance scale', 'justice', 'law'],\r\n    ru: ['весы', 'справедливость', 'закон']\r\n  },\r\n  '🪜': {\r\n    en: ['ladder', 'climb', 'height'],\r\n    ru: ['лестница', 'лазить', 'высота']\r\n  },\r\n  '🧰': {\r\n    en: ['toolbox', 'tools', 'repair'],\r\n    ru: ['ящик с инструментами', 'инструменты', 'ремонт']\r\n  },\r\n  '🔧': {\r\n    en: ['wrench', 'repair', 'tools'],\r\n    ru: ['гаечный ключ', 'ремонт', 'инструменты']\r\n  },\r\n  '🔨': {\r\n    en: ['hammer', 'build', 'fix'],\r\n    ru: ['молоток', 'строить', 'чинить']\r\n  },\r\n  '⚒️': {\r\n    en: ['hammer and pick', 'construction', 'work'],\r\n    ru: ['молот и кирка', 'строительство', 'работа']\r\n  },\r\n  '🛠️': {\r\n    en: ['tools', 'fix', 'maintenance'],\r\n    ru: ['инструменты', 'чинить', 'обслуживание']\r\n  },\r\n  '⛏️': {\r\n    en: ['pickaxe', 'mining', 'digging'],\r\n    ru: ['кирка', 'горное дело', 'копать']\r\n  },\r\n\r\n  // Writing & Reading\r\n  '✏️': {\r\n    en: ['pencil', 'writing', 'notes'],\r\n    ru: ['карандаш', 'написание', 'заметки']\r\n  },\r\n  '🖊️': {\r\n    en: ['pen', 'writing', 'signature'],\r\n    ru: ['ручка', 'написание', 'подпись']\r\n  },\r\n  '🖋️': {\r\n    en: ['fountain pen', 'calligraphy', 'writing'],\r\n    ru: ['перьевая ручка', 'каллиграфия', 'написание']\r\n  },\r\n  '✒️': {\r\n    en: ['nib', 'ink', 'writing'],\r\n    ru: ['остриё', 'чернила', 'написание']\r\n  },\r\n  '🖌️': {\r\n    en: ['paintbrush', 'art', 'painting'],\r\n    ru: ['кисть', 'искусство', 'живопись']\r\n  },\r\n  '🖍️': {\r\n    en: ['crayon', 'drawing', 'color'],\r\n    ru: ['мелки', 'рисование', 'цвет']\r\n  },\r\n  '📝': {\r\n    en: ['memo', 'notes', 'document'],\r\n    ru: ['заметка', 'записи', 'документ']\r\n  },\r\n  '📚': {\r\n    en: ['books', 'reading', 'library'],\r\n    ru: ['книги', 'чтение', 'библиотека']\r\n  },\r\n  '📖': {\r\n    en: ['open book', 'reading', 'story'],\r\n    ru: ['открытая книга', 'чтение', 'история']\r\n  },\r\n  '🔖': {\r\n    en: ['bookmark', 'reading', 'save'],\r\n    ru: ['закладка', 'чтение', 'сохранить']\r\n  },\r\n  '📑': {\r\n    en: ['bookmark tabs', 'pages', 'notes'],\r\n    ru: ['закладки', 'страницы', 'заметки']\r\n  },\r\n  '🗒️': {\r\n    en: ['spiral notepad', 'notes', 'journal'],\r\n    ru: ['спиральный блокнот', 'записи', 'журнал']\r\n  },\r\n  '📄': {\r\n    en: ['document', 'paper', 'page'],\r\n    ru: ['документ', 'бумага', 'страница']\r\n  },\r\n  '📰': {\r\n    en: ['newspaper', 'news', 'journalism'],\r\n    ru: ['газета', 'новости', 'журналистика']\r\n  },\r\n  '🗞️': {\r\n    en: ['rolled newspaper', 'press', 'print'],\r\n    ru: ['свернутая газета', 'пресса', 'печатное издание']\r\n  },\r\n  '📁': {\r\n    en: ['file folder', 'documents', 'storage'],\r\n    ru: ['папка', 'документы', 'хранение']\r\n  },\r\n  '📂': {\r\n    en: ['open folder', 'organization', 'files'],\r\n    ru: ['открытая папка', 'организация', 'файлы']\r\n  },\r\n  '🗂️': {\r\n    en: ['card index', 'catalog', 'records'],\r\n    ru: ['карточный индекс', 'каталог', 'записи']\r\n  },\r\n  '🕐': {\r\n    en: ['1 o\\'clock', 'clock face', 'time'],\r\n    ru: ['час', 'циферблат', 'время']\r\n  },\r\n  '🕑': {\r\n    en: ['2 o\\'clock', 'clock face', 'time'],\r\n    ru: ['два часа', 'циферблат', 'время']\r\n  },\r\n  '🕒': {\r\n    en: ['3 o\\'clock', 'clock face', 'time'],\r\n    ru: ['три часа', 'циферблат', 'время']\r\n  },\r\n  '🕓': {\r\n    en: ['4 o\\'clock', 'clock face', 'time'],\r\n    ru: ['четыре часа', 'циферблат', 'время']\r\n  },\r\n  '🕔': {\r\n    en: ['5 o\\'clock', 'clock face', 'time'],\r\n    ru: ['пять часов', 'циферблат', 'время']\r\n  },\r\n  '🕕': {\r\n    en: ['6 o\\'clock', 'clock face', 'time'],\r\n    ru: ['шесть часов', 'циферблат', 'время']\r\n  },\r\n  '🕖': {\r\n    en: ['7 o\\'clock', 'clock face', 'time'],\r\n    ru: ['семь часов', 'циферблат', 'время']\r\n  },\r\n  '🕗': {\r\n    en: ['8 o\\'clock', 'clock face', 'time'],\r\n    ru: ['восемь часов', 'циферблат', 'время']\r\n  },\r\n  '🕘': {\r\n    en: ['9 o\\'clock', 'clock face', 'time'],\r\n    ru: ['девять часов', 'циферблат', 'время']\r\n  },\r\n  '🕙': {\r\n    en: ['10 o\\'clock', 'clock face', 'time'],\r\n    ru: ['десять часов', 'циферблат', 'время']\r\n  },\r\n  '🕚': {\r\n    en: ['11 o\\'clock', 'clock face', 'time'],\r\n    ru: ['одиннадцать часов', 'циферблат', 'время']\r\n  },\r\n  '🕛': {\r\n    en: ['12 o\\'clock', 'clock face', 'time'],\r\n    ru: ['двенадцать часов', 'циферблат', 'время']\r\n  },\r\n  '🕰️': {\r\n    en: ['mantel clock', 'clock', 'time'],\r\n    ru: ['настенные часы', 'часы', 'время']\r\n  },\r\n  '⏰': {\r\n    en: ['alarm clock', 'wake up', 'time'],\r\n    ru: ['будильник', 'пробуждение', 'время']\r\n  },\r\n  '⏱️': {\r\n    en: ['stopwatch', 'timer', 'time'],\r\n    ru: ['секундомер', 'таймер', 'время']\r\n  },\r\n  '⏲️': {\r\n    en: ['kitchen timer', 'timer', 'time'],\r\n    ru: ['кухонный таймер', 'таймер', 'время']\r\n  },\r\n  '⌚': {\r\n    en: ['watch', 'smartwatch', 'time'],\r\n    ru: ['часы', 'умные часы', 'время']\r\n  },\r\n\r\n  //------------------------- Symbols & Signs -------------------------\r\n  // Hearts\r\n  '❤️': {\r\n    en: ['heart', 'love', 'affection'],\r\n    ru: ['сердце', 'любовь', 'нежность']\r\n  },\r\n  '🧡': {\r\n    en: ['orange heart', 'warmth', 'care'],\r\n    ru: ['оранжевое сердце', 'теплота', 'забота']\r\n  },\r\n  '💛': {\r\n    en: ['yellow heart', 'friendship', 'happiness'],\r\n    ru: ['жёлтое сердце', 'дружба', 'счастье']\r\n  },\r\n  '💚': {\r\n    en: ['green heart', 'envy', 'nature', 'eco'],\r\n    ru: ['зелёное сердце', 'зависть', 'природа', 'эко']\r\n  },\r\n  '💙': {\r\n    en: ['blue heart', 'loyalty', 'trust'],\r\n    ru: ['синее сердце', 'верность', 'доверие']\r\n  },\r\n  '💜': {\r\n    en: ['purple heart', 'compassion', 'admiration'],\r\n    ru: ['фиолетовое сердце', 'сострадание', 'восхищение']\r\n  },\r\n  '🤎': {\r\n    en: ['brown heart', 'earth', 'stability'],\r\n    ru: ['коричневое сердце', 'земля', 'устойчивость']\r\n  },\r\n  '🖤': {\r\n    en: ['black heart', 'mourning', 'dark'],\r\n    ru: ['чёрное сердце', 'скорбь', 'тёмный']\r\n  },\r\n  '🤍': {\r\n    en: ['white heart', 'pure', 'peace'],\r\n    ru: ['белое сердце', 'чистый', 'мир']\r\n  },\r\n  '💔': {\r\n    en: ['broken heart', 'sad', 'heartbreak'],\r\n    ru: ['разбитое сердце', 'грусть', 'сердечная боль']\r\n  },\r\n  '❣️': {\r\n    en: ['heart exclamation', 'love', 'emotion'],\r\n    ru: ['сердечный восклицательный знак', 'любовь', 'эмоция']\r\n  },\r\n  '💕': {\r\n    en: ['two hearts', 'romance', 'affection'],\r\n    ru: ['два сердца', 'романтика', 'нежность']\r\n  },\r\n  '💞': {\r\n    en: ['revolving hearts', 'love', 'relationship'],\r\n    ru: ['вращающиеся сердца', 'любовь', 'отношения']\r\n  },\r\n  '💓': {\r\n    en: ['beating heart', 'emotion', 'passion'],\r\n    ru: ['бьющееся сердце', 'эмоция', 'страсть']\r\n  },\r\n  '💗': {\r\n    en: ['growing heart', 'love', 'expanding'],\r\n    ru: ['растущее сердце', 'любовь', 'расширяющееся']\r\n  },\r\n  '💖': {\r\n    en: ['sparkling heart', 'admiration', 'shine'],\r\n    ru: ['блестящее сердце', 'восхищение', 'сияние']\r\n  },\r\n  '💘': {\r\n    en: ['heart with arrow', 'love', 'romance'],\r\n    ru: ['сердце со стрелой', 'любовь', 'романтика']\r\n  },\r\n  '💝': {\r\n    en: ['heart with ribbon', 'gift', 'affection'],\r\n    ru: ['сердце с лентой', 'подарок', 'нежность']\r\n  },\r\n  '💟': {\r\n    en: ['heart decoration', 'symbol', 'love'],\r\n    ru: ['украшение сердца', 'символ', 'любовь']\r\n  },\r\n\r\n  // Religion\r\n  '☮️': {\r\n    en: ['peace', 'pacifism', 'symbol'],\r\n    ru: ['мир', 'пацифизм', 'символ']\r\n  },\r\n  '✝️': {\r\n    en: ['cross', 'christianity', 'religion'],\r\n    ru: ['крест', 'христианство', 'религия']\r\n  },\r\n  '☪️': {\r\n    en: ['star and crescent', 'islam', 'faith'],\r\n    ru: ['звезда и полумесяц', 'ислам', 'вера']\r\n  },\r\n  '🕉️': {\r\n    en: ['om', 'hinduism', 'spiritual'],\r\n    ru: ['ом', 'индуизм', 'духовный']\r\n  },\r\n  '☸️': {\r\n    en: ['dharma wheel', 'buddhism', 'karma'],\r\n    ru: ['колесо дхармы', 'буддизм', 'карма']\r\n  },\r\n  '✡️': {\r\n    en: ['star of david', 'judaism', 'faith'],\r\n    ru: ['звезда Давида', 'иудаизм', 'вера']\r\n  },\r\n  '🔯': {\r\n    en: ['hexagram', 'mystic', 'spiritual'],\r\n    ru: ['шестиконечная звезда', 'мистический', 'духовный']\r\n  },\r\n  '🕎': {\r\n    en: ['menorah', 'hanukkah', 'jewish'],\r\n    ru: ['менора', 'Ханука', 'еврейский']\r\n  },\r\n  '☯️': {\r\n    en: ['yin yang', 'balance', 'harmony'],\r\n    ru: ['инь-ян', 'баланс', 'гармония']\r\n  },\r\n  '☦️': {\r\n    en: ['orthodox cross', 'christianity', 'faith'],\r\n    ru: ['православный крест', 'христианство', 'вера']\r\n  },\r\n  '🛐': {\r\n    en: ['place of worship', 'religion', 'faith'],\r\n    ru: ['место поклонения', 'религия', 'вера']\r\n  },\r\n  '⛎': {\r\n    en: ['ophiuchus', 'zodiac', 'astrology'],\r\n    ru: ['змееносец', 'зодиак', 'астрология']\r\n  },\r\n\r\n  // Warning & Restrictions\r\n  '⚠️': {\r\n    en: ['warning', 'caution', 'alert'],\r\n    ru: ['предупреждение', 'осторожность', 'тревога']\r\n  },\r\n  '🚸': {\r\n    en: ['children crossing', 'school', 'pedestrian'],\r\n    ru: ['дети на переходе', 'школа', 'пешеход']\r\n  },\r\n  '⛔': {\r\n    en: ['no entry', 'prohibited', 'restricted'],\r\n    ru: ['вход запрещён', 'запрещено', 'ограничено']\r\n  },\r\n  '🚫': {\r\n    en: ['prohibited', 'no', 'forbidden'],\r\n    ru: ['запрещено', 'нет', 'запрещено']\r\n  },\r\n  '☢️': {\r\n    en: ['radioactive', 'hazard', 'danger'],\r\n    ru: ['радиоактивный', 'опасность', 'угроза']\r\n  },\r\n  '☣️': {\r\n    en: ['biohazard', 'toxic', 'warning'],\r\n    ru: ['биологическая опасность', 'ядовитый', 'предупреждение']\r\n  },\r\n\r\n  // Math Symbols\r\n  '➕': {\r\n    en: ['plus', 'addition', 'math'],\r\n    ru: ['плюс', 'сложение', 'математика']\r\n  },\r\n  '➖': {\r\n    en: ['minus', 'subtraction', 'math'],\r\n    ru: ['минус', 'вычитание', 'математика']\r\n  },\r\n  '➗': {\r\n    en: ['division', 'divide', 'math'],\r\n    ru: ['деление', 'делить', 'математика']\r\n  },\r\n  '✖️': {\r\n    en: ['multiplication', 'times', 'math'],\r\n    ru: ['умножение', 'раз', 'математика']\r\n  },\r\n  '♾️': {\r\n    en: ['infinity', 'limitless', 'math'],\r\n    ru: ['бесконечность', 'безграничный', 'математика']\r\n  },\r\n  '💲': {\r\n    en: ['dollar', 'money', 'currency'],\r\n    ru: ['доллар', 'деньги', 'валюта']\r\n  },\r\n  '💱': {\r\n    en: ['currency exchange', 'finance', 'money'],\r\n    ru: ['обмен валюты', 'финансы', 'деньги']\r\n  },\r\n\r\n  // Arrows\r\n  '⬆️': {\r\n    en: ['up arrow', 'increase', 'direction'],\r\n    ru: ['стрелка вверх', 'увеличение', 'направление']\r\n  },\r\n  '↗️': {\r\n    en: ['up-right arrow', 'growth', 'move'],\r\n    ru: ['стрелка вверх-вправо', 'рост', 'движение']\r\n  },\r\n  '➡️': {\r\n    en: ['right arrow', 'next', 'forward'],\r\n    ru: ['стрелка вправо', 'далее', 'вперёд']\r\n  },\r\n  '↘️': {\r\n    en: ['down-right arrow', 'decrease', 'move'],\r\n    ru: ['стрелка вниз-вправо', 'уменьшение', 'движение']\r\n  },\r\n  '⬇️': {\r\n    en: ['down arrow', 'lower', 'decline'],\r\n    ru: ['стрелка вниз', 'понижение', 'снижение']\r\n  },\r\n  '↙️': {\r\n    en: ['down-left arrow', 'falling', 'move'],\r\n    ru: ['стрелка вниз-влево', 'падение', 'движение']\r\n  },\r\n  '⬅️': {\r\n    en: ['left arrow', 'back', 'previous'],\r\n    ru: ['стрелка влево', 'назад', 'предыдущий']\r\n  },\r\n  '↖️': {\r\n    en: ['up-left arrow', 'direction', 'move'],\r\n    ru: ['стрелка вверх-влево', 'направление', 'движение']\r\n  },\r\n  '↕️': {\r\n    en: ['vertical arrows', 'up down', 'change'],\r\n    ru: ['вертикальные стрелки', 'вверх вниз', 'изменение']\r\n  },\r\n  '↔️': {\r\n    en: ['horizontal arrows', 'left right', 'switch'],\r\n    ru: ['горизонтальные стрелки', 'влево вправо', 'переключение']\r\n  },\r\n  '↩️': {\r\n    en: ['back arrow', 'undo', 'return'],\r\n    ru: ['стрелка назад', 'отмена', 'возврат']\r\n  },\r\n  '↪️': {\r\n    en: ['right curved arrow', 'redirect', 'turn'],\r\n    ru: ['изогнутая стрелка вправо', 'перенаправление', 'поворот']\r\n  },\r\n  '⤴️': {\r\n    en: ['up-right arrow', 'diagonal', 'move'],\r\n    ru: ['диагональная стрелка вверх-вправо', 'диагональ', 'движение']\r\n  },\r\n  '⤵️': {\r\n    en: ['down-right arrow', 'diagonal', 'move'],\r\n    ru: ['диагональная стрелка вниз-вправо', 'диагональ', 'движение']\r\n  },\r\n  '🔃': {\r\n    en: ['repeat', 'cycle', 'refresh'],\r\n    ru: ['повтор', 'цикл', 'обновление']\r\n  },\r\n  '🔄': {\r\n    en: ['counterclockwise arrows', 'reload', 'sync'],\r\n    ru: ['стрелки против часовой стрелки', 'перезагрузка', 'синхронизация']\r\n  },\r\n\r\n  // Miscellaneous Symbols\r\n  '🔆': {\r\n    en: ['bright', 'high brightness', 'sun'],\r\n    ru: ['яркий', 'высокая яркость', 'солнце']\r\n  },\r\n  '📶': {\r\n    en: ['signal', 'network', 'connection'],\r\n    ru: ['сигнал', 'сеть', 'соединение']\r\n  },\r\n  '🎦': {\r\n    en: ['cinema', 'movies', 'entertainment'],\r\n    ru: ['кино', 'фильмы', 'развлечения']\r\n  },\r\n  '🔅': {\r\n    en: ['dim', 'low brightness', 'light'],\r\n    ru: ['тусклый', 'низкая яркость', 'свет']\r\n  },\r\n  '♻️': {\r\n    en: ['recycle', 'eco', 'sustainability'],\r\n    ru: ['переработка', 'эко', 'устойчивость']\r\n  },\r\n  '✅': {\r\n    en: ['check mark', 'yes', 'approved'],\r\n    ru: ['галочка', 'да', 'одобрено']\r\n  },\r\n  '❌': {\r\n    en: ['cross mark', 'no', 'wrong'],\r\n    ru: ['крестик', 'нет', 'неправильно']\r\n  },\r\n  '❎': {\r\n    en: ['negative cross', 'decline', 'cancel'],\r\n    ru: ['отрицательный крест', 'отказ', 'отмена']\r\n  },\r\n  '➰': {\r\n    en: ['curly loop', 'infinity', 'twist'],\r\n    ru: ['извилистая петля', 'бесконечность', 'скрутка']\r\n  },\r\n  '➿': {\r\n    en: ['double curly loop', 'loop', 'repeat'],\r\n    ru: ['двойная извилистая петля', 'петля', 'повтор']\r\n  },\r\n  '〽️': {\r\n    en: ['part alternation', 'music', 'symbol'],\r\n    ru: ['знак чередования', 'музыка', 'символ']\r\n  },\r\n  '✳️': {\r\n    en: ['eight-spoked asterisk', 'star', 'highlight'],\r\n    ru: ['восьмиконечная астериска', 'звезда', 'акцент']\r\n  },\r\n  '✴️': {\r\n    en: ['eight-pointed star', 'shine', 'special'],\r\n    ru: ['восьмиконечная звезда', 'сияние', 'особый']\r\n  },\r\n  '❇️': {\r\n    en: ['sparkle', 'highlight', 'shine'],\r\n    ru: ['сверкание', 'акцент', 'сияние']\r\n  },\r\n  '©️': {\r\n    en: ['copyright', 'legal', 'rights'],\r\n    ru: ['авторское право', 'юридический', 'права']\r\n  },\r\n  '®️': {\r\n    en: ['registered', 'trademark', 'brand'],\r\n    ru: ['зарегистрировано', 'торговая марка', 'бренд']\r\n  },\r\n  '™️': {\r\n    en: ['trademark', 'brand', 'symbol'],\r\n    ru: ['торговая марка', 'бренд', 'символ']\r\n  },\r\n\r\n  //------------------------- Flags -------------------------\r\n  // Special Flags\r\n  '🏁': {\r\n    en: ['checkered flag', 'finish line', 'racing'],\r\n    ru: ['шашечный флаг', 'финишная черта', 'гонки']\r\n  },\r\n  '🚩': {\r\n    en: ['triangular flag', 'mark', 'warning'],\r\n    ru: ['треугольный флаг', 'метка', 'предупреждение']\r\n  },\r\n  '🎌': {\r\n    en: ['crossed flags', 'celebration', 'Japan'],\r\n    ru: ['перекрещённые флаги', 'празднование', 'Япония']\r\n  },\r\n  '🏴': {\r\n    en: ['black flag', 'protest', 'symbol'],\r\n    ru: ['чёрный флаг', 'протест', 'символ']\r\n  },\r\n  '🏳️': {\r\n    en: ['white flag', 'surrender', 'peace'],\r\n    ru: ['белый флаг', 'капитуляция', 'мир']\r\n  },\r\n  '🏳️‍🌈': {\r\n    en: ['rainbow flag', 'LGBTQ+', 'pride'],\r\n    ru: ['радужный флаг', 'ЛГБТК+', 'гордость']\r\n  },\r\n  '🏳️‍⚧️': {\r\n    en: ['transgender flag', 'trans', 'pride'],\r\n    ru: ['трансгендерный флаг', 'транс', 'гордость']\r\n  },\r\n  '🏴‍☠️': {\r\n    en: ['pirate flag', 'skull', 'danger'],\r\n    ru: ['пиратский флаг', 'череп', 'опасность']\r\n  },\r\n\r\n  // Country Flags (Sample)\r\n  '🇺🇸': {\r\n    en: ['United States', 'USA', 'America'],\r\n    ru: ['Соединённые Штаты', 'США', 'Америка']\r\n  },\r\n  '🇬🇧': {\r\n    en: ['United Kingdom', 'UK', 'Britain'],\r\n    ru: ['Соединённое Королевство', 'Великобритания', 'Британия']\r\n  },\r\n  '🇯🇵': {\r\n    en: ['Japan', 'Japanese', 'Asia'],\r\n    ru: ['Япония', 'японский', 'Азия']\r\n  },\r\n  '🇰🇷': {\r\n    en: ['South Korea', 'Korea', 'Asian'],\r\n    ru: ['Южная Корея', 'Корея', 'азиатский']\r\n  },\r\n  '🇩🇪': {\r\n    en: ['Germany', 'Deutschland', 'Europe'],\r\n    ru: ['Германия', 'Германия', 'Европа']\r\n  },\r\n  '🇨🇳': {\r\n    en: ['China', 'Chinese', 'Asia'],\r\n    ru: ['Китай', 'китайский', 'Азия']\r\n  },\r\n  '🇧🇷': {\r\n    en: ['Brazil', 'Brasil', 'South America'],\r\n    ru: ['Бразилия', 'Бразилия', 'Южная Америка']\r\n  },\r\n  '🇮🇳': {\r\n    en: ['India', 'Indian', 'Asia'],\r\n    ru: ['Индия', 'индийский', 'Азия']\r\n  },\r\n  '🇫🇷': {\r\n    en: ['France', 'French', 'Europe'],\r\n    ru: ['Франция', 'французский', 'Европа']\r\n  },\r\n  '🇪🇸': {\r\n    en: ['Spain', 'Spanish', 'Europe'],\r\n    ru: ['Испания', 'испанский', 'Европа']\r\n  },\r\n  '🇮🇹': {\r\n    en: ['Italy', 'Italian', 'Europe'],\r\n    ru: ['Италия', 'итальянский', 'Европа']\r\n  },\r\n  '🇷🇺': {\r\n    en: ['Russia', 'Russian', 'Europe'],\r\n    ru: ['Россия', 'русский', 'Европа']\r\n  },\r\n  '🇨🇦': {\r\n    en: ['Canada', 'Canadian', 'North America'],\r\n    ru: ['Канада', 'канадский', 'Северная Америка']\r\n  },\r\n  '🇦🇺': {\r\n    en: ['Australia', 'Aussie', 'Oceania'],\r\n    ru: ['Австралия', 'австралиец', 'Океания']\r\n  },\r\n  '🇳🇿': {\r\n    en: ['New Zealand', 'Kiwi', 'Oceania'],\r\n    ru: ['Новая Зеландия', 'киви', 'Океания']\r\n  },\r\n  // Additional Country Flags (Extended)\r\n  '🇲🇽': {\r\n    en: ['Mexico', 'Mexican', 'Latin America'],\r\n    ru: ['Мексика', 'мексиканский', 'Латинская Америка']\r\n  },\r\n  '🇦🇷': {\r\n    en: ['Argentina', 'Argentinian', 'South America'],\r\n    ru: ['Аргентина', 'аргентинский', 'Южная Америка']\r\n  },\r\n  '🇵🇰': {\r\n    en: ['Pakistan', 'Pakistani', 'Asia'],\r\n    ru: ['Пакистан', 'пакистанский', 'Азия']\r\n  },\r\n  '🇪🇬': {\r\n    en: ['Egypt', 'Egyptian', 'Africa'],\r\n    ru: ['Египет', 'египетский', 'Африка']\r\n  },\r\n  '🇸🇪': {\r\n    en: ['Sweden', 'Swedish', 'Europe'],\r\n    ru: ['Швеция', 'шведский', 'Европа']\r\n  },\r\n  '🇳🇴': {\r\n    en: ['Norway', 'Norwegian', 'Europe'],\r\n    ru: ['Норвегия', 'норвежский', 'Европа']\r\n  },\r\n  '🇳🇱': {\r\n    en: ['Netherlands', 'Dutch', 'Europe'],\r\n    ru: ['Нидерланды', 'голландский', 'Европа']\r\n  },\r\n  '🇨🇭': {\r\n    en: ['Switzerland', 'Swiss', 'Europe'],\r\n    ru: ['Швейцария', 'швейцарский', 'Европа']\r\n  },\r\n  '🇹🇷': {\r\n    en: ['Turkey', 'Turkish', 'Eurasia'],\r\n    ru: ['Турция', 'турецкий', 'Евразия']\r\n  },\r\n  '🇮🇩': {\r\n    en: ['Indonesia', 'Indonesian', 'Asia'],\r\n    ru: ['Индонезия', 'индонезийский', 'Азия']\r\n  },\r\n  '🇸🇬': {\r\n    en: ['Singapore', 'Singaporean', 'Asia'],\r\n    ru: ['Сингапур', 'сингапурский', 'Азия']\r\n  },\r\n  '🇮🇱': {\r\n    en: ['Israel', 'Israeli', 'Middle East'],\r\n    ru: ['Израиль', 'израильский', 'Ближний Восток']\r\n  },\r\n  '🇵🇹': {\r\n    en: ['Portugal', 'Portuguese', 'Europe'],\r\n    ru: ['Португалия', 'португальский', 'Европа']\r\n  },\r\n  '🇵🇱': {\r\n    en: ['Poland', 'Polish', 'Europe'],\r\n    ru: ['Польша', 'польский', 'Европа']\r\n  },\r\n  '🇹🇭': {\r\n    en: ['Thailand', 'Thai', 'Asia'],\r\n    ru: ['Таиланд', 'тайский', 'Азия']\r\n  }\r\n};\r\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/data/emojiData.js?");

/***/ }),

/***/ "./src/data/icons.js":
/*!***************************!*\
  !*** ./src/data/icons.js ***!
  \***************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   closeSVG: () => (/* binding */ closeSVG),\n/* harmony export */   collapseSVG: () => (/* binding */ collapseSVG),\n/* harmony export */   expandSVG: () => (/* binding */ expandSVG),\n/* harmony export */   helpSVG: () => (/* binding */ helpSVG),\n/* harmony export */   openSVG: () => (/* binding */ openSVG),\n/* harmony export */   sendSVG: () => (/* binding */ sendSVG)\n/* harmony export */ });\nconst svgUrl = \"http://www.w3.org/2000/svg\";\r\nconst iconSize = 24;\r\n\r\n// SVG icon\r\nconst buttonAccentColdColor = \"#096AD9\";\r\nconst buttonOpenedColor = \"#82B32A\";\r\nconst buttonClosedColor = \"#B34A2A\";\r\nconst sendSVG = `\r\n  <svg xmlns=\"${svgUrl}\" \r\n      width=\"${iconSize}\" \r\n      height=\"${iconSize}\" \r\n      viewBox=\"0 0 250 250\">\r\n    <path fill=\"${buttonAccentColdColor}\" d=\"M22.32 98.04l-19.04 -87.15c-0.75,-3.46 0.48,-6.84 3.29,-9 2.81,-2.17 6.39,-2.49 9.55,-0.87l225.95 116.02c3.07,1.57 4.87,4.52 4.87,7.96 0,3.44 -1.8,6.39 -4.87,7.96l-225.95 116.02c-3.16,1.62 -6.74,1.3 -9.55,-0.87 -2.81,-2.16 -4.04,-5.54 -3.29,-9l19.04 -87.15c0.79,-3.62 3.53,-6.26 7.18,-6.91l102.6 -18.19c0.91,-0.16 1.56,-0.94 1.56,-1.86 0,-0.92 -0.65,-1.7 -1.56,-1.86l-102.6 -18.19c-3.65,-0.65 -6.39,-3.29 -7.18,-6.91z\"/>\r\n  </svg>\r\n`;\r\n\r\nconst closeSVG = `\r\n  <svg xmlns=\"${svgUrl}\" \r\n       width=\"${iconSize / 1.6}\" \r\n       height=\"${iconSize / 1.6}\" \r\n       viewBox=\"0 0 250 250\" \r\n       style=\"shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd\">\r\n      <path fill=\"${buttonOpenedColor}\" d=\"M46.62 0l156.76 0c25.64,0 46.62,20.98 46.62,46.62l0 156.75c0,25.65 -20.98,46.63 -46.62,46.63l-156.76 0c-25.64,0 -46.62,-20.98 -46.62,-46.63l0 -156.75c0,-25.64 20.98,-46.62 46.62,-46.62zm45.71 70.24l32.67 32.67 32.67 -32.67c2.73,-2.73 7.18,-2.73 9.91,0l12.18 12.18c2.73,2.73 2.73,7.18 0,9.91l-32.67 32.67 32.67 32.66c2.73,2.74 2.73,7.19 0,9.92l-12.18 12.18c-2.73,2.73 -7.18,2.73 -9.91,0l-32.67 -32.67 -32.67 32.67c-2.73,2.73 -7.18,2.73 -9.91,0l-12.18 -12.18c-2.73,-2.73 -2.73,-7.18 0,-9.92l32.67 -32.66 -32.67 -32.67c-2.73,-2.73 -2.73,-7.18 0,-9.91l12.18 -12.18c2.73,-2.73 7.18,-2.73 9.91,0z\"/>\r\n  </svg>\r\n`;\r\n\r\nconst openSVG = `\r\n  <svg xmlns=\"${svgUrl}\" \r\n       width=\"${iconSize / 1.6}\" \r\n       height=\"${iconSize / 1.6}\" \r\n       viewBox=\"0 0 250 250\" \r\n       style=\"shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd\">\r\n      <path fill=\"${buttonClosedColor}\" d=\"M46.62 0l156.76 0c25.64,0 46.62,20.98 46.62,46.62l0 156.75c0,25.65 -20.98,46.63 -46.62,46.63l-156.76 0c-25.64,0 -46.62,-20.98 -46.62,-46.63l0 -156.75c0,-25.64 20.98,-46.62 46.62,-46.62zm15.5 135.79l57.92 -57.93c2.73,-2.73 7.19,-2.72 9.92,0.01l57.92 57.92c2.73,2.73 2.73,7.18 0,9.91l-12.18 12.18c-2.73,2.73 -7.18,2.73 -9.92,0l-35.82 -35.83c-2.73,-2.73 -7.19,-2.73 -9.92,0l-35.82 35.83c-2.74,2.73 -7.19,2.73 -9.92,0l-12.18 -12.18c-2.73,-2.73 -2.73,-7.18 0,-9.91z\"/>\r\n  </svg>\r\n`;\r\n\r\nconst collapseSVG = `\r\n  <svg xmlns=\"${svgUrl}\" \r\n       width=\"${iconSize / 1.6}\" \r\n       height=\"${iconSize / 1.6}\" \r\n       viewBox=\"0 0 250 250\"\r\n       style=\"shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd\">\r\n    <path fill=\"${buttonOpenedColor}\" d=\"M46.62 0l156.76 0c25.64,0 46.62,20.98 46.62,46.62l0 156.75c0,25.65 -20.98,46.63 -46.62,46.63l-156.76 0c-25.64,0 -46.62,-20.98 -46.62,-46.63l0 -156.75c0,-25.64 20.98,-46.62 46.62,-46.62zm109.99 181.69l-75.07 0c-7.3,0 -13.23,-5.92 -13.23,-13.22l0 -75.08c0,-2.35 1.92,-4.28 4.28,-4.28l17.9 0c2.35,0 4.27,1.93 4.27,4.28l0 32.81c0,1.77 1.01,3.28 2.64,3.96 1.63,0.67 3.42,0.33 4.66,-0.93l59.68 -59.68c1.67,-1.65 4.38,-1.65 6.05,0l12.66 12.66c1.66,1.67 1.66,4.38 0,6.05l-59.68 59.68c-1.26,1.24 -1.6,3.03 -0.93,4.66 0.68,1.63 2.19,2.64 3.96,2.64l32.81 0c2.35,0 4.28,1.92 4.28,4.28l0 17.9c0,2.36 -1.93,4.28 -4.28,4.28z\"/>\r\n  </svg>\r\n`;\r\n\r\nconst expandSVG = `\r\n  <svg xmlns=\"${svgUrl}\" \r\n       width=\"${iconSize / 1.6}\" \r\n       height=\"${iconSize / 1.6}\" \r\n       viewBox=\"0 0 250 250\"\r\n       style=\"shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd\">\r\n    <path fill=\"${buttonClosedColor}\" d=\"M46.62 0l156.76 0c25.64,0 46.62,20.98 46.62,46.62l0 156.75c0,25.65 -20.98,46.63 -46.62,46.63l-156.76 0c-25.64,0 -46.62,-20.98 -46.62,-46.63l0 -156.75c0,-25.64 20.98,-46.62 46.62,-46.62zm46.77 68.31l75.07 0c7.3,0 13.23,5.92 13.23,13.22l0 75.08c0,2.35 -1.92,4.28 -4.28,4.28l-17.9 0c-2.35,0 -4.27,-1.93 -4.27,-4.28l0 -32.81c0,-1.77 -1.01,-3.28 -2.64,-3.96 -1.63,-0.67 -3.42,-0.33 -4.66,0.93l-59.68 59.68c-1.67,1.65 -4.38,1.65 -6.05,0l-12.66 -12.66c-1.66,-1.67 -1.66,-4.38 0,-6.05l59.68 -59.68c1.26,-1.24 1.6,-3.03 0.93,-4.66 -0.68,-1.63 -2.19,-2.64 -3.96,-2.64l-32.81 0c-2.35,0 -4.28,-1.92 -4.28,-4.27l0 -17.9c0,-2.36 1.93,-4.28 4.28,-4.28z\"/>\r\n  </svg>\r\n`;\r\n\r\nconst helpSVG = `\r\n  <svg xmlns=\"${svgUrl}\" \r\n       width=\"${iconSize / 1.6}\" \r\n       height=\"${iconSize / 1.6}\" \r\n       viewBox=\"0 0 250 250\"\r\n       style=\"shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd\">\r\n    <path fill=\"${buttonOpenedColor}\" d=\"M46.59 0l156.82 0c25.65,0 46.59,20.94 46.59,46.59l0 156.82c0,25.65 -20.94,46.59 -46.59,46.59l-156.82 0c-25.65,0 -46.59,-20.94 -46.59,-46.59l0 -156.82c0,-25.65 20.94,-46.59 46.59,-46.59zm130.96 87.85c0,6.57 -1.03,12.72 -3.08,18.06 -2.05,5.34 -4.93,9.85 -8.42,13.75 -3.69,3.9 -8,7.39 -13.13,10.47 -4.11,2.46 -8.83,4.93 -13.96,6.98 -1.64,0.82 -2.87,2.46 -2.87,4.51l0 15.19c0,2.67 -2.06,4.72 -4.73,4.72l-25.04 0c-2.66,0 -4.92,-2.05 -4.92,-4.72l0 -25.65c0,-2.26 1.44,-3.9 3.49,-4.52 2.87,-1.02 5.95,-2.05 9.23,-3.28 4.52,-1.85 8.62,-3.9 12.11,-6.57 3.9,-2.67 6.78,-5.75 9.24,-9.24 2.46,-3.49 3.49,-7.59 3.49,-12.31 0,-6.78 -2.05,-11.7 -6.36,-14.78 -4.31,-2.88 -10.06,-4.52 -17.65,-4.52 -5.75,0 -11.7,1.24 -18.07,3.7 -5.33,2.05 -9.85,4.31 -13.13,6.36 -1.03,0.62 -2.26,0.62 -3.29,0 -1.02,-0.62 -1.64,-1.64 -1.64,-2.87l0 -23.2c0,-2.05 1.23,-3.9 3.08,-4.51 4.1,-1.44 9.44,-3.08 15.8,-4.52 8.42,-2.05 17.24,-3.07 26.89,-3.07 8.42,0 16.01,1.02 22.58,3.07 6.36,2.06 12.11,4.72 16.62,8.42 4.52,3.49 7.8,7.8 10.27,12.72 2.26,4.73 3.49,10.06 3.49,15.81l0 0zm-46.19 114.12l-25.04 0c-2.67,0 -4.92,-2.05 -4.92,-4.72l0 -17.24c0,-2.67 2.25,-4.72 4.92,-4.72l25.04 0c2.67,0 4.72,2.05 4.72,4.72l0 17.24c0,2.67 -2.05,4.72 -4.72,4.72z\"/>\r\n  </svg>\r\n`;\r\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/data/icons.js?");

/***/ }),

/***/ "./src/helpers/chatUsernameColors.js":
/*!*******************************************!*\
  !*** ./src/helpers/chatUsernameColors.js ***!
  \*******************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   mentionColors: () => (/* binding */ mentionColors),\n/* harmony export */   optimizeColor: () => (/* binding */ optimizeColor),\n/* harmony export */   usernameColors: () => (/* binding */ usernameColors)\n/* harmony export */ });\nconst colorUtils = {\r\n  // Convert HSL (with h in [0,360] and s,l in percentage numbers) to hex\r\n  hslToHex(h, s, l) {\r\n    s /= 100;\r\n    l /= 100;\r\n    const c = (1 - Math.abs(2 * l - 1)) * s;\r\n    const x = c * (1 - Math.abs((h / 60) % 2 - 1));\r\n    const m = l - c / 2;\r\n    let r, g, b;\r\n    if (h < 60) {\r\n      r = c; g = x; b = 0;\r\n    } else if (h < 120) {\r\n      r = x; g = c; b = 0;\r\n    } else if (h < 180) {\r\n      r = 0; g = c; b = x;\r\n    } else if (h < 240) {\r\n      r = 0; g = x; b = c;\r\n    } else if (h < 300) {\r\n      r = x; g = 0; b = c;\r\n    } else {\r\n      r = c; g = 0; b = x;\r\n    }\r\n    r = Math.round((r + m) * 255);\r\n    g = Math.round((g + m) * 255);\r\n    b = Math.round((b + m) * 255);\r\n    return '#' + ((1 << 24) + (r << 16) + (g << 8) + b)\r\n      .toString(16).slice(1);\r\n  },\r\n\r\n  // Convert hex to HSL object {h, s, l}\r\n  hexToHSL(hex) {\r\n    hex = hex.replace(/^#/, '');\r\n    if (hex.length === 3) {\r\n      hex = hex.split('').map(c => c + c).join('');\r\n    }\r\n    const r = parseInt(hex.substring(0, 2), 16) / 255;\r\n    const g = parseInt(hex.substring(2, 4), 16) / 255;\r\n    const b = parseInt(hex.substring(4, 6), 16) / 255;\r\n    const max = Math.max(r, g, b);\r\n    const min = Math.min(r, g, b);\r\n    let h = 0;\r\n    let s = 0;\r\n    const l = (max + min) / 2;\r\n\r\n    if (max !== min) {\r\n      const d = max - min;\r\n      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\r\n\r\n      if (max === r) {\r\n        h = ((g - b) / d + (g < b ? 6 : 0)) * 60;\r\n      } else if (max === g) {\r\n        h = ((b - r) / d + 2) * 60;\r\n      } else {\r\n        h = ((r - g) / d + 4) * 60;\r\n      }\r\n    }\r\n\r\n    return { h: Math.round(h), s: s * 100, l: l * 100 };\r\n  },\r\n\r\n  // Extract just the hue from a hex color\r\n  hexToHue(hex) {\r\n    return this.hexToHSL(hex).h;\r\n  },\r\n\r\n  // Calculate relative luminance from hex for accessibility calculations\r\n  getLuminance(hex) {\r\n    hex = hex.replace(\"#\", \"\");\r\n    const r = parseInt(hex.slice(0, 2), 16) / 255;\r\n    const g = parseInt(hex.slice(2, 4), 16) / 255;\r\n    const b = parseInt(hex.slice(4, 6), 16) / 255;\r\n    const convert = c => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\r\n    return 0.2126 * convert(r) + 0.7152 * convert(g) + 0.0722 * convert(b);\r\n  },\r\n\r\n  // Calculate contrast ratio between two colors\r\n  contrastRatio(fg, bg) {\r\n    const L1 = this.getLuminance(fg);\r\n    const L2 = this.getLuminance(bg);\r\n    return (Math.max(L1, L2) + 0.05) / (Math.min(L1, L2) + 0.05);\r\n  }\r\n};\r\n\r\n// Color generator factory function (not exported)\r\nfunction colorGenerator(config) {\r\n  // Use sessionStorage as required\r\n  const storageKey = config.storageKey || 'usernameColors';\r\n  let colorMap;\r\n  try {\r\n    const stored = sessionStorage.getItem(storageKey);\r\n    colorMap = stored ? JSON.parse(stored) : {};\r\n  } catch (e) {\r\n    colorMap = {};\r\n  }\r\n\r\n  // Allow the entire hue circle by default.\r\n  const hueRanges = config.hueRanges || [{ min: 0, max: 360 }];\r\n\r\n  // Calculate total available hue space\r\n  const totalHueSpace = hueRanges.reduce((sum, range) =>\r\n    sum + (range.max - range.min), 0);\r\n\r\n  const hasFixedSaturation = Boolean(config.saturation);\r\n  const hasFixedLightness = Boolean(config.lightness);\r\n  const minSat = config.minSaturation || 40;\r\n  const maxSat = config.maxSaturation || 90;\r\n  const minLight = config.minLightness || 55;\r\n  const maxLight = config.maxLightness || 80;\r\n  const satRange = maxSat - minSat;\r\n  const lightRange = maxLight - minLight;\r\n\r\n  // Cache existing color values and used hues for quick comparison\r\n  const existingColorValues = new Set(Object.values(colorMap));\r\n  const usedHues = new Set();\r\n\r\n  // Extract used hues from stored hex values by converting back to hue\r\n  Object.values(colorMap).forEach(colorStr => {\r\n    try {\r\n      usedHues.add(colorUtils.hexToHue(colorStr));\r\n    } catch (e) {\r\n      // Ignore parsing errors\r\n    }\r\n  });\r\n\r\n  // Helper to generate a random hue from the allowed ranges\r\n  function generateRandomHue() {\r\n    // Generate a random value within the total available hue space\r\n    let randomValue = Math.floor(Math.random() * totalHueSpace);\r\n\r\n    // Map this random value to the correct range\r\n    for (let range of hueRanges) {\r\n      const rangeSize = range.max - range.min;\r\n      if (randomValue < rangeSize) {\r\n        // Map the value to the range\r\n        return range.min + randomValue;\r\n      }\r\n      // Move to the next range\r\n      randomValue -= rangeSize;\r\n    }\r\n\r\n    // Fallback (should never happen if ranges are configured correctly)\r\n    return hueRanges[0].min;\r\n  }\r\n\r\n  return {\r\n    getColor(username) {\r\n      // If username is falsy, return a default color (converted to hex)\r\n      if (!username) {\r\n        const satVal = hasFixedSaturation ? parseInt(config.saturation, 10) : 50;\r\n        let lightVal = hasFixedLightness ? parseInt(config.lightness, 10) : 50;\r\n        // Ensure default follows the rule if hue is 0 (which is not in 210–280, so no check needed)\r\n        return colorUtils.hslToHex(0, satVal, lightVal);\r\n      }\r\n\r\n      // Normalize the username to ensure consistency\r\n      const key = username.trim().toLowerCase();\r\n\r\n      // First, check localStorage\r\n      let localColors = {};\r\n      try {\r\n        const storedLocal = localStorage.getItem(storageKey);\r\n        localColors = storedLocal ? JSON.parse(storedLocal) : {};\r\n      } catch (e) {\r\n        // Ignore parsing errors\r\n      }\r\n      if (localColors[key]) {\r\n        return localColors[key];\r\n      }\r\n\r\n      // Next, check sessionStorage cache from colorMap\r\n      if (colorMap[key]) {\r\n        return colorMap[key];\r\n      }\r\n\r\n      let color = null;\r\n      let attempts = 0;\r\n\r\n      // Try to find a unique color (max 10 attempts)\r\n      while (!color && attempts < 10) {\r\n        // Generate a hue from the allowed ranges\r\n        let hue;\r\n        if (usedHues.size >= totalHueSpace) {\r\n          // If all possible hues are used, pick a random one from allowed ranges\r\n          hue = generateRandomHue();\r\n        } else {\r\n          // Try to find an unused hue within allowed ranges\r\n          do {\r\n            hue = generateRandomHue();\r\n          } while (usedHues.has(hue) && usedHues.size < totalHueSpace);\r\n        }\r\n\r\n        // Generate saturation as a number\r\n        const satVal = hasFixedSaturation ? parseInt(config.saturation, 10) :\r\n          Math.floor(Math.random() * satRange) + minSat;\r\n\r\n        // Generate lightness with a special rule:\r\n        // For hues between 210 and 280, the lightness must be at least 65.\r\n        let lightVal;\r\n        if (hasFixedLightness) {\r\n          lightVal = parseInt(config.lightness, 10);\r\n          if (hue >= 210 && hue < 280 && lightVal < 65) {\r\n            lightVal = 65;\r\n          }\r\n        } else {\r\n          let effectiveMinLight = minLight;\r\n          if (hue >= 210 && hue < 280) {\r\n            effectiveMinLight = Math.max(minLight, 65);\r\n          }\r\n          const effectiveLightRange = maxLight - effectiveMinLight;\r\n          lightVal = Math.floor(Math.random() * effectiveLightRange) + effectiveMinLight;\r\n        }\r\n\r\n        const newColor = colorUtils.hslToHex(hue, satVal, lightVal);\r\n\r\n        // Check if this color is unique\r\n        if (!existingColorValues.has(newColor)) {\r\n          color = newColor;\r\n          usedHues.add(hue);\r\n          break;\r\n        }\r\n        attempts++;\r\n      }\r\n\r\n      // Fallback if unique color not found in allotted attempts\r\n      if (!color) {\r\n        const hue = generateRandomHue();\r\n        const satVal = hasFixedSaturation ? parseInt(config.saturation, 10) :\r\n          Math.floor(Math.random() * satRange) + minSat;\r\n        let lightVal;\r\n        if (hasFixedLightness) {\r\n          lightVal = parseInt(config.lightness, 10);\r\n          if (hue >= 210 && hue < 280 && lightVal < 65) {\r\n            lightVal = 65;\r\n          }\r\n        } else {\r\n          let effectiveMinLight = minLight;\r\n          if (hue >= 210 && hue < 280) {\r\n            effectiveMinLight = Math.max(minLight, 65);\r\n          }\r\n          const effectiveLightRange = maxLight - effectiveMinLight;\r\n          lightVal = Math.floor(Math.random() * effectiveLightRange) + effectiveMinLight;\r\n        }\r\n        color = colorUtils.hslToHex(hue, satVal, lightVal);\r\n      }\r\n\r\n      // Save the new color\r\n      colorMap[key] = color;\r\n      existingColorValues.add(color);\r\n\r\n      // Batch update to sessionStorage with throttling\r\n      this.saveColors();\r\n\r\n      return color;\r\n    },\r\n\r\n    // Use a debounced save to reduce writes to sessionStorage\r\n    saveTimeout: null,\r\n    saveColors() {\r\n      if (this.saveTimeout) clearTimeout(this.saveTimeout);\r\n      this.saveTimeout = setTimeout(() => {\r\n        try {\r\n          sessionStorage.setItem(storageKey, JSON.stringify(colorMap));\r\n        } catch (e) {\r\n          // Handle potential storage errors silently\r\n        }\r\n      }, 500);\r\n    }\r\n  };\r\n}\r\n\r\n// Darken the color until it meets 4.5:1 contrast on white (exported)\r\nconst optimizeColor = hex => {\r\n  console.log(\"Optimizing color for contrast:\", hex);\r\n  let { h, s, l } = colorUtils.hexToHSL(hex);\r\n  let newHex = hex;\r\n  while (colorUtils.contrastRatio(newHex, \"#FFFFFF\") < 4.5 && l > 0) {\r\n    newHex = colorUtils.hslToHex(h, s, --l);\r\n  }\r\n  return newHex;\r\n};\r\n\r\n// Pre-configured color generators (exported)\r\nconst usernameColors = colorGenerator({\r\n  storageKey: 'usernameColors',\r\n  minSaturation: 40,\r\n  maxSaturation: 90,\r\n  minLightness: 55,\r\n  maxLightness: 80\r\n});\r\n\r\nconst mentionColors = colorGenerator({\r\n  storageKey: 'mentionColors',\r\n  saturation: '80',\r\n  lightness: '60'\r\n});\n\n//# sourceURL=webpack://tampermonkey-script/./src/helpers/chatUsernameColors.js?");

/***/ }),

/***/ "./src/helpers/helpers.js":
/*!********************************!*\
  !*** ./src/helpers/helpers.js ***!
  \********************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   addChatToggleFeature: () => (/* binding */ addChatToggleFeature),\n/* harmony export */   addViewportMeta: () => (/* binding */ addViewportMeta),\n/* harmony export */   adjustVisibility: () => (/* binding */ adjustVisibility),\n/* harmony export */   applyFontSize: () => (/* binding */ applyFontSize),\n/* harmony export */   banned: () => (/* binding */ banned),\n/* harmony export */   base64Encode: () => (/* binding */ base64Encode),\n/* harmony export */   calibrateToMoscowTime: () => (/* binding */ calibrateToMoscowTime),\n/* harmony export */   checkImageExists: () => (/* binding */ checkImageExists),\n/* harmony export */   checkIsMobile: () => (/* binding */ checkIsMobile),\n/* harmony export */   clamp: () => (/* binding */ clamp),\n/* harmony export */   compactXML: () => (/* binding */ compactXML),\n/* harmony export */   createFontSizeControl: () => (/* binding */ createFontSizeControl),\n/* harmony export */   createLengthPopup: () => (/* binding */ createLengthPopup),\n/* harmony export */   createNewMessagesSeparator: () => (/* binding */ createNewMessagesSeparator),\n/* harmony export */   debounce: () => (/* binding */ debounce),\n/* harmony export */   decodeEncodedURL: () => (/* binding */ decodeEncodedURL),\n/* harmony export */   decodeURL: () => (/* binding */ decodeURL),\n/* harmony export */   enterPrivateMode: () => (/* binding */ enterPrivateMode),\n/* harmony export */   exitPrivateMode: () => (/* binding */ exitPrivateMode),\n/* harmony export */   extractUserId: () => (/* binding */ extractUserId),\n/* harmony export */   extractUsername: () => (/* binding */ extractUsername),\n/* harmony export */   fetchJSON: () => (/* binding */ fetchJSON),\n/* harmony export */   focusTextInput: () => (/* binding */ focusTextInput),\n/* harmony export */   generateRandomString: () => (/* binding */ generateRandomString),\n/* harmony export */   getChatState: () => (/* binding */ getChatState),\n/* harmony export */   getExactUserIdByName: () => (/* binding */ getExactUserIdByName),\n/* harmony export */   getRandomEmojiAvatar: () => (/* binding */ getRandomEmojiAvatar),\n/* harmony export */   getRandomInterval: () => (/* binding */ getRandomInterval),\n/* harmony export */   handleElementsBehavior: () => (/* binding */ handleElementsBehavior),\n/* harmony export */   handleMobileLayout: () => (/* binding */ handleMobileLayout),\n/* harmony export */   handlePrivateMessageInput: () => (/* binding */ handlePrivateMessageInput),\n/* harmony export */   highlightMentionWords: () => (/* binding */ highlightMentionWords),\n/* harmony export */   initChatLengthPopupEvents: () => (/* binding */ initChatLengthPopupEvents),\n/* harmony export */   isEncodedURL: () => (/* binding */ isEncodedURL),\n/* harmony export */   isTrustedDomain: () => (/* binding */ isTrustedDomain),\n/* harmony export */   notification: () => (/* binding */ notification),\n/* harmony export */   observeMessagesPanel: () => (/* binding */ observeMessagesPanel),\n/* harmony export */   parseUsername: () => (/* binding */ parseUsername),\n/* harmony export */   playAudio: () => (/* binding */ playAudio),\n/* harmony export */   privateMessageState: () => (/* binding */ privateMessageState),\n/* harmony export */   removeNewMessagesSeparator: () => (/* binding */ removeNewMessagesSeparator),\n/* harmony export */   restoreChatState: () => (/* binding */ restoreChatState),\n/* harmony export */   restoreFontSize: () => (/* binding */ restoreFontSize),\n/* harmony export */   saveChatState: () => (/* binding */ saveChatState),\n/* harmony export */   scrollToBottom: () => (/* binding */ scrollToBottom),\n/* harmony export */   setupCommandEvents: () => (/* binding */ setupCommandEvents),\n/* harmony export */   setupPrivateMessageEvents: () => (/* binding */ setupPrivateMessageEvents),\n/* harmony export */   setupRandomEmojiAttention: () => (/* binding */ setupRandomEmojiAttention),\n/* harmony export */   showChatAlert: () => (/* binding */ showChatAlert),\n/* harmony export */   sleep: () => (/* binding */ sleep),\n/* harmony export */   toggleChatMaximize: () => (/* binding */ toggleChatMaximize),\n/* harmony export */   toggleChatVisibility: () => (/* binding */ toggleChatVisibility)\n/* harmony export */ });\n/* harmony import */ var _converters_image_converter_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../converters/image-converter.js */ \"./src/converters/image-converter.js\");\n/* harmony import */ var _converters_video_converter_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../converters/video-converter.js */ \"./src/converters/video-converter.js\");\n/* harmony import */ var _data_definitions_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../data/definitions.js */ \"./src/data/definitions.js\");\n/* harmony import */ var _data_icons_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../data/icons.js */ \"./src/data/icons.js\");\n/* harmony import */ var _data_animations_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../data/animations.js */ \"./src/data/animations.js\");\n/* harmony import */ var _data_emojiData_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../data/emojiData.js */ \"./src/data/emojiData.js\");\n/* harmony import */ var _auth_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../auth.js */ \"./src/auth.js\");\n/* harmony import */ var _components_chatUsernameColorsPanel_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../components/chatUsernameColorsPanel.js */ \"./src/components/chatUsernameColorsPanel.js\");\n/* harmony import */ var _chatUsernameColors_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./chatUsernameColors.js */ \"./src/helpers/chatUsernameColors.js\");\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n// ==================================================================================================\r\n\r\nlet lastEmojiAvatar = null;\r\nfunction getRandomEmojiAvatar() {\r\n  let newEmoji;\r\n  do {\r\n    newEmoji = _data_definitions_js__WEBPACK_IMPORTED_MODULE_2__.emojiFaces[Math.floor(Math.random() * _data_definitions_js__WEBPACK_IMPORTED_MODULE_2__.emojiFaces.length)];\r\n  } while (newEmoji === lastEmojiAvatar);\r\n  lastEmojiAvatar = newEmoji;\r\n  return newEmoji;\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction handleElementsBehavior() {\r\n  const wrapper = document.querySelector('#app-chat-container .chat-wrapper');\r\n  if (!wrapper) return;\r\n  const chatContainer = document.querySelector('#app-chat-container');\r\n  const isNarrow = wrapper.offsetWidth <= 780;\r\n  const isVeryNarrow = wrapper.offsetWidth <= 380;\r\n  const isExtremelyNarrow = wrapper.offsetWidth <= 340;\r\n  const isMaximized = chatContainer.classList.contains('maximized');\r\n  const userList = document.querySelector('#app-chat-container .user-list-container');\r\n  const systemMessages = document.querySelectorAll('#app-chat-container .message.system');\r\n  let isUserListOpen = false;\r\n\r\n  // Handle user list for narrow screens\r\n  if (userList) {\r\n    if (isNarrow) {\r\n      userList.style.position = 'absolute';\r\n      userList.style.height = '100%';\r\n      userList.style.top = '0';\r\n      userList.style.right = '0';\r\n      userList.style.transition = 'transform 0.3s ease';\r\n      userList.style.zIndex = '1001';\r\n      userList.style.transform = 'translateX(100%)';\r\n\r\n      // Apply alignment to all system messages if they exist\r\n      if (systemMessages && systemMessages.length > 0) {\r\n        systemMessages.forEach(message => {\r\n          message.style.setProperty('align-items', 'start', 'important');\r\n        });\r\n      }\r\n\r\n      let revealButton = document.querySelector('#app-chat-container .reveal-userlist-btn');\r\n      if (!revealButton) {\r\n        revealButton = document.createElement('button');\r\n        revealButton.className = 'reveal-userlist-btn hidden-userlist';\r\n        revealButton.textContent = '📋';\r\n        chatContainer.appendChild(revealButton);\r\n        function closeUserList(event) {\r\n          if (!userList.contains(event.target) && event.target !== revealButton) {\r\n            userList.style.transform = 'translateX(100%)';\r\n            revealButton.classList.remove('shown-userlist');\r\n            revealButton.classList.add('hidden-userlist');\r\n            isUserListOpen = false;\r\n            document.removeEventListener('click', closeUserList, true);\r\n          }\r\n        }\r\n        revealButton.addEventListener('click', (event) => {\r\n          event.stopPropagation();\r\n          if (!isUserListOpen) {\r\n            userList.style.transform = 'translateX(0)';\r\n            revealButton.classList.remove('hidden-userlist');\r\n            revealButton.classList.add('shown-userlist');\r\n            isUserListOpen = true;\r\n            setTimeout(() => {\r\n              document.addEventListener('click', closeUserList, true);\r\n            }, 10);\r\n          }\r\n        });\r\n      }\r\n    } else {\r\n      userList.style.position = '';\r\n      userList.style.height = '';\r\n      userList.style.top = '';\r\n      userList.style.right = '';\r\n      userList.style.transform = '';\r\n      userList.style.zIndex = '';\r\n\r\n      // Remove alignment property from all system messages if they exist\r\n      if (systemMessages && systemMessages.length > 0) {\r\n        systemMessages.forEach(message => {\r\n          message.style.removeProperty('align-items');\r\n        });\r\n      }\r\n\r\n      const revealButton = document.querySelector('#app-chat-container .reveal-userlist-btn');\r\n      if (revealButton) {\r\n        revealButton.remove();\r\n      }\r\n    }\r\n  }\r\n\r\n  // Adjust message layout for narrow screens\r\n  document.querySelectorAll('#app-chat-container .message').forEach(msg => {\r\n    const msgText = msg.querySelector('.message-text');\r\n\r\n    msg.style.flexDirection = (isNarrow) ? 'column' : 'row';\r\n    msg.style.marginBottom = (isNarrow) ? '0.8em' : '0';\r\n    msgText.style.marginTop = (isNarrow) ? '0.2em' : '0';\r\n  });\r\n\r\n  // Apply scaling to video containers and YouTube thumbnails\r\n  const mediaElements = document.querySelectorAll('#app-chat-container .video-container, #app-chat-container .youtube-thumb');\r\n\r\n  mediaElements.forEach(element => {\r\n    if (isExtremelyNarrow) {\r\n      element.style.transformOrigin = 'left';\r\n      element.style.transform = 'scale(0.8)';\r\n      element.style.maxWidth = '100%';\r\n    } else if (isVeryNarrow) {\r\n      element.style.transformOrigin = 'left';\r\n      element.style.transform = 'scale(0.9)';\r\n      element.style.maxWidth = '100%';\r\n    } else {\r\n      element.style.transformOrigin = '';\r\n      element.style.transform = '';\r\n      element.style.maxWidth = '';\r\n    }\r\n  });\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction observeMessagesPanel() {\r\n  const messagesPanel = document.getElementById('messages-panel');\r\n  if (!messagesPanel) return;\r\n\r\n  const observer = new MutationObserver(() => {\r\n    handleElementsBehavior();\r\n    (0,_converters_video_converter_js__WEBPACK_IMPORTED_MODULE_1__.convertVideoLinksToPlayer)();\r\n    (0,_converters_image_converter_js__WEBPACK_IMPORTED_MODULE_0__.convertImageLinksToImage)();\r\n    scrollToBottom(250);\r\n  });\r\n\r\n  observer.observe(messagesPanel, { childList: true, subtree: true });\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction restoreChatState() {\r\n  const chat = document.getElementById('app-chat-container');\r\n  const toggleButton = document.querySelector('.chat-toggle-button');\r\n  if (!chat || !toggleButton) return;\r\n\r\n  const state = getChatState();\r\n  const viewportWidth = window.innerWidth;\r\n  const viewportHeight = window.innerHeight;\r\n  const computedStyle = getComputedStyle(document.documentElement);\r\n  const minWidth = parseInt(computedStyle.getPropertyValue('--min-chat-width')) || 250;\r\n  const minHeight = parseInt(computedStyle.getPropertyValue('--min-chat-height')) || 200;\r\n\r\n  chat.style.width = Math.min(viewportWidth, Math.max(minWidth, state.width)) + 'px';\r\n  chat.style.height = Math.min(viewportHeight, Math.max(minHeight, state.height)) + 'px';\r\n  chat.style.left = clamp(state.left, 0, viewportWidth - chat.offsetWidth) + 'px';\r\n\r\n  if (state.floating) {\r\n    chat.style.top = clamp(state.top, 0, viewportHeight - chat.offsetHeight) + 'px';\r\n    chat.style.bottom = '';\r\n    chat.classList.add(\"floating-chat\");\r\n    chat.style.display = state.isVisible ? 'flex' : 'none';\r\n    chat.style.opacity = state.isVisible ? '1' : '0';\r\n    toggleButton.innerHTML = state.isVisible ? _data_icons_js__WEBPACK_IMPORTED_MODULE_3__.closeSVG : _data_icons_js__WEBPACK_IMPORTED_MODULE_3__.openSVG;\r\n  } else {\r\n    chat.style.bottom = '0';\r\n    chat.style.top = '';\r\n    chat.classList.remove(\"floating-chat\");\r\n    chat.classList.remove('visible-chat', 'hidden-chat');\r\n    chat.classList.add(state.isVisible ? 'visible-chat' : 'hidden-chat');\r\n    toggleButton.innerHTML = state.isVisible ? _data_icons_js__WEBPACK_IMPORTED_MODULE_3__.closeSVG : _data_icons_js__WEBPACK_IMPORTED_MODULE_3__.openSVG;\r\n  }\r\n\r\n  handleElementsBehavior();\r\n}\r\n\r\n// 1. Update helpers.js to include the font size in the getChatState function\r\n\r\n// In getChatState() function in helpers.js\r\nfunction getChatState() {\r\n  const savedState = localStorage.getItem('chatState');\r\n  const defaultState = {\r\n    height: 300,\r\n    width: Math.min(window.innerWidth, 600),\r\n    left: 0,\r\n    floating: false,\r\n    top: window.innerHeight - 300,\r\n    isVisible: true,\r\n    fontSizeMultiplier: 1.0 // Add the new default font size multiplier\r\n  };\r\n\r\n  return savedState ? { ...defaultState, ...JSON.parse(savedState) } : defaultState;\r\n}\r\n\r\n// 2. Add a function to apply font size changes\r\nfunction applyFontSize(multiplier) {\r\n  const chatContainer = document.getElementById('app-chat-container');\r\n  const messageInput = document.getElementById('message-input');\r\n  if (!chatContainer) return;\r\n\r\n  // Apply font size to the main container\r\n  chatContainer.style.fontSize = `${multiplier}em`;\r\n\r\n  // Apply base font size to message input without multiplier\r\n  // since it inherits the multiplier from the container\r\n  if (messageInput) {\r\n    messageInput.style.fontSize = '1em';\r\n  }\r\n\r\n  // Save the current multiplier in the chat state\r\n  const chatState = getChatState();\r\n  saveChatState({\r\n    ...chatState,\r\n    fontSizeMultiplier: multiplier\r\n  });\r\n}\r\n\r\n// 3. Add a function to restore font size from state on initialization\r\nfunction restoreFontSize() {\r\n  const chatState = getChatState();\r\n  applyFontSize(chatState.fontSizeMultiplier);\r\n}\r\n\r\n// 4. Function to create the font size slider\r\nfunction createFontSizeControl() {\r\n  const chatContainer = document.getElementById('app-chat-container');\r\n  if (!chatContainer) return;\r\n\r\n  const chatState = getChatState();\r\n\r\n  // Create font size control container\r\n  const fontSizeControl = document.createElement('div');\r\n  fontSizeControl.className = 'font-size-control';\r\n\r\n  // Create the slider\r\n  const fontSlider = document.createElement('input');\r\n  fontSlider.type = 'range';\r\n  fontSlider.min = '0.8';\r\n  fontSlider.max = '1.5';\r\n  fontSlider.step = '0.1';\r\n  fontSlider.value = chatState.fontSizeMultiplier;\r\n  fontSlider.className = 'font-size-slider';\r\n\r\n  // Prevent dragging the chat when interacting with the slider\r\n  fontSlider.addEventListener('mousedown', (e) => {\r\n    e.stopPropagation();\r\n  });\r\n\r\n  // Update font size on input change\r\n  fontSlider.addEventListener('input', (e) => {\r\n    const value = parseFloat(e.target.value);\r\n    applyFontSize(value);\r\n  });\r\n\r\n  // Append the slider to the control container\r\n  fontSizeControl.appendChild(fontSlider);\r\n\r\n  // Add the control container to the chat drag area\r\n  const dragArea = document.querySelector('.chat-drag-area');\r\n  if (dragArea) {\r\n    dragArea.appendChild(fontSizeControl);\r\n  }\r\n}\r\n\r\nfunction saveChatState(state) {\r\n  localStorage.setItem('chatState', JSON.stringify(state));\r\n}\r\n\r\nfunction clamp(value, min, max) {\r\n  return Math.min(Math.max(value, min), max);\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction parseUsername(username) {\r\n  if (typeof username !== 'string') return username;\r\n  return username.replace(/^\\d+#/, '');\r\n}\r\n\r\n// Extract userId from JID, handling formats like \"123456#Username\"\r\nfunction extractUserId(jid) {\r\n  if (!jid) return null;\r\n  const parts = jid.split('/');\r\n  if (parts.length < 2) return null;\r\n\r\n  const secondPart = parts[1];\r\n  return secondPart.split('#')[0]; // Get everything before the # character\r\n}\r\n\r\n// Extract username from the full JID or login string\r\nfunction extractUsername(login) {\r\n  if (!login) return \"Unknown\";\r\n\r\n  // If login contains #, get everything after it\r\n  if (login.includes('#')) {\r\n    return login.split('#')[1];\r\n  }\r\n\r\n  // Replace the parseUsername call with direct implementation\r\n  return login.replace(/^\\d+#/, '');\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction adjustVisibility(element, action, opacity) {\r\n  if (!element) return;\r\n  void element.offsetHeight; // Force reflow\r\n  element.style.transition = 'opacity 0.3s ease';\r\n  element.style.opacity = action === 'show' ? opacity : '0';\r\n  if (action === 'hide') {\r\n    element.addEventListener('transitionend', () => {\r\n      if (element.style.opacity === '0' && element.parentNode) {\r\n        element.parentNode.removeChild(element);\r\n      }\r\n    }, { once: true });\r\n  }\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nconst isTrustedDomain = url => {\r\n  try {\r\n    const { hostname } = new URL(url);\r\n    const domain = hostname.toLowerCase().split('.').slice(-2).join('.');\r\n    return { isTrusted: _data_definitions_js__WEBPACK_IMPORTED_MODULE_2__.trustedDomains.includes(domain), domain };\r\n  } catch (err) {\r\n    console.error(\"Error in isTrustedDomain:\", err.message);\r\n    return { isTrusted: false, domain: url };\r\n  }\r\n};\r\n\r\n// ==================================================================================================\r\n\r\nfunction isEncodedURL(url) {\r\n  const urlPattern = /^https?:\\/\\//;\r\n  const encodedPattern = /%[0-9A-Fa-f]{2}/;\r\n  return urlPattern.test(url) && encodedPattern.test(url);\r\n}\r\n\r\nfunction decodeURL(url) {\r\n  const [base] = url.split('#');\r\n  return decodeURIComponent(base).replace(/ /g, '_');\r\n}\r\n\r\nfunction decodeEncodedURL(text) {\r\n  return text.replace(/\\b(https?:\\/\\/[^\\s]+)/gi, match =>\r\n    isEncodedURL(match) ? decodeURL(match) : match\r\n  );\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nconst notification = 'https://github.com/VimiummuimiV/KG_Chat_Application/raw/refs/heads/main/src/sounds/notification-pluck-on.mp3';\r\nconst banned = 'https://github.com/VimiummuimiV/KG_Chat_Application/raw/refs/heads/main/src/sounds/mario-game-over.mp3';\r\n\r\nfunction playAudio(url) {\r\n  const audio = new Audio(url);\r\n  audio.volume = 1;\r\n  audio.play();\r\n}\r\n\r\nfunction highlightMentionWords() {\r\n  const container = document.getElementById('messages-panel');\r\n  if (!container) return;\r\n\r\n  // Get username from auth data\r\n  const authData = localStorage.getItem('klavoauth');\r\n  let username = '';\r\n  try {\r\n    if (authData) {\r\n      const parsedAuth = JSON.parse(authData);\r\n      if (parsedAuth && parsedAuth.username) {\r\n        username = extractUsername(parsedAuth.username);\r\n      }\r\n    }\r\n  } catch (e) {\r\n    console.error('Error parsing auth data:', e);\r\n  }\r\n\r\n  // Don't proceed if no username to check\r\n  if (!username) return;\r\n\r\n  // Use username as the only term to highlight\r\n  const highlightTerms = [username];\r\n  const globalProcessed = new WeakSet();\r\n\r\n  const messages = container.querySelectorAll('.message-text:not(.processed-for-mention)');\r\n  messages.forEach((message) => {\r\n    const walker = document.createTreeWalker(\r\n      message,\r\n      NodeFilter.SHOW_TEXT,\r\n      {\r\n        acceptNode: (node) => {\r\n          if (globalProcessed.has(node)) return NodeFilter.FILTER_SKIP;\r\n          const parent = node.parentElement;\r\n          if (parent.closest('.mention, .time, .username')) {\r\n            return NodeFilter.FILTER_SKIP;\r\n          }\r\n          return NodeFilter.FILTER_ACCEPT;\r\n        }\r\n      }\r\n    );\r\n\r\n    const nodes = [];\r\n    let currentNode;\r\n    while ((currentNode = walker.nextNode())) nodes.push(currentNode);\r\n\r\n    if (nodes.length > 0) {\r\n      nodes.forEach((node) => {\r\n        if (!globalProcessed.has(node)) {\r\n          processNode(node, highlightTerms);\r\n          globalProcessed.add(node);\r\n        }\r\n      });\r\n\r\n      // Mark this message as processed\r\n      message.classList.add('processed-for-mention');\r\n    }\r\n  });\r\n\r\n  function processNode(node, keywords) {\r\n    const regex = /(@?[\\wа-яА-ЯёЁ'-]+)|[\\s]+|[^@\\s\\wа-яА-ЯёЁ'-]+/gu;\r\n    const tokens = node.textContent.match(regex) || [];\r\n    const fragment = document.createDocumentFragment();\r\n\r\n    tokens.forEach(token => {\r\n      const isMatch = keywords.some(keyword =>\r\n        keyword.localeCompare(token, undefined, { sensitivity: 'accent' }) === 0\r\n      );\r\n\r\n      if (isMatch) {\r\n        const mentionSpan = document.createElement('span');\r\n        mentionSpan.className = 'mention';\r\n\r\n        token.split('').forEach(char => {\r\n          const charSpan = document.createElement('span');\r\n          charSpan.style.color = _chatUsernameColors_js__WEBPACK_IMPORTED_MODULE_8__.mentionColors.getColor(char);\r\n          charSpan.textContent = char;\r\n          mentionSpan.appendChild(charSpan);\r\n        });\r\n\r\n        fragment.appendChild(mentionSpan);\r\n      } else {\r\n        fragment.appendChild(document.createTextNode(token));\r\n      }\r\n    });\r\n\r\n    node.parentNode.replaceChild(fragment, node);\r\n  }\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction scrollToBottom(scrollThreshold) {\r\n  const container = document.getElementById('messages-panel');\r\n  if (!container) return;\r\n\r\n  const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;\r\n  if (distanceFromBottom <= scrollThreshold) {\r\n    container.scrollTop = container.scrollHeight;\r\n  }\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction showChatAlert(message, options = {}) {\r\n  const dragArea = document.querySelector('.chat-drag-area');\r\n  if (!dragArea) return;\r\n\r\n  const existingAlert = dragArea.querySelector('.chat-dynamic-alert');\r\n  if (existingAlert && existingAlert.parentNode === dragArea) {\r\n    dragArea.removeChild(existingAlert);\r\n  }\r\n\r\n  const defaultOptions = { type: 'info', duration: 3000 };\r\n  const settings = { ...defaultOptions, ...options };\r\n\r\n  const colorMap = {\r\n    info: '#2196F3',\r\n    warning: '#FF9800',\r\n    error: '#F44336',\r\n    success: '#4CAF50'\r\n  };\r\n\r\n  const alertElement = document.createElement('div');\r\n  alertElement.className = 'chat-dynamic-alert';\r\n  alertElement.innerHTML = message;\r\n\r\n  alertElement.style.cssText = `\r\n    position: absolute;\r\n    white-space: nowrap;\r\n    top: 50%;\r\n    left: 50%;\r\n    transform: translate(-50%, -50%);\r\n    color: ${colorMap[settings.type] || colorMap.info};\r\n    padding: 5px 10px;\r\n    border-radius: 3px;\r\n    z-index: 1000;\r\n    font-family: \"Montserrat\", sans-serif;\r\n    font-size: 10px;\r\n    font-weight: 500;\r\n    opacity: 0;\r\n  `;\r\n\r\n  dragArea.appendChild(alertElement);\r\n\r\n  function animateAlert() {\r\n    requestAnimationFrame(() => {\r\n      alertElement.style.transition = 'all 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55)';\r\n      alertElement.style.opacity = '1';\r\n      alertElement.style.transform = 'translate(-50%, -50%)';\r\n\r\n      setTimeout(() => {\r\n        alertElement.style.transition = 'transform 0.05s ease-in-out';\r\n        const shakeSequence = [\r\n          { x: 5, delay: 0 },\r\n          { x: -7, delay: 50 },\r\n          { x: 9, delay: 100 },\r\n          { x: -6, delay: 150 },\r\n          { x: 4, delay: 200 },\r\n          { x: -3, delay: 250 },\r\n          { x: 0, delay: 300 }\r\n        ];\r\n\r\n        shakeSequence.forEach((move) => {\r\n          setTimeout(() => {\r\n            alertElement.style.transform = `translate(calc(-50% + ${move.x}px), -50%)`;\r\n          }, move.delay);\r\n        });\r\n      }, 300);\r\n\r\n      setTimeout(() => {\r\n        alertElement.style.transition = 'opacity 0.3s ease-in-out';\r\n        alertElement.style.opacity = '0';\r\n        setTimeout(() => {\r\n          if (alertElement && alertElement.parentNode === dragArea) {\r\n            dragArea.removeChild(alertElement);\r\n          }\r\n        }, 300);\r\n      }, settings.duration);\r\n    });\r\n  }\r\n\r\n  animateAlert();\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction focusTextInput() {\r\n  const chatContainer = document.getElementById('app-chat-container');\r\n  const element = document.getElementById('message-input');\r\n  if (element && chatContainer && chatContainer.style.display !== 'none') {\r\n    element.focus();\r\n    return true;\r\n  }\r\n  return false;\r\n}\r\n\r\n// ==================================================================================================\r\n\r\n// Helper to fetch JSON and validate response\r\nasync function fetchJSON(url) {\r\n  const response = await fetch(url);\r\n  if (!response.ok) throw new Error(`Failed to fetch ${url}`);\r\n  return response.json();\r\n}\r\n\r\n// Helper function to get Exact user ID by username via the search API\r\nasync function getExactUserIdByName(userName) {\r\n  // Define the search API URL\r\n  const searchApiUrl = `https://klavogonki.ru/api/profile/search-users?query=${encodeURIComponent(userName)}`;\r\n\r\n  try {\r\n    // Get search results from the API\r\n    const searchResults = await fetchJSON(searchApiUrl);\r\n\r\n    // Ensure search results exist and contain data\r\n    if (!searchResults.all?.length) {\r\n      throw new Error(`User ${userName} not found.`);\r\n    }\r\n\r\n    // Return the ID of the user with the exact matching login\r\n    const user = searchResults.all.find(user => user.login === userName);\r\n    if (!user) {\r\n      throw new Error(`Exact match for user ${userName} not found.`);\r\n    }\r\n\r\n    return user.id;\r\n  } catch (error) {\r\n    // console.error('Error getting user ID:', error);\r\n    showChatAlert(`Could not find user \"${userName}\"`, { type: 'error', duration: 5000 });\r\n    return null;\r\n  }\r\n}\r\n\r\n// State management for private messaging\r\nconst privateMessageState = {\r\n  isPrivateMode: false,\r\n  targetUsername: null,\r\n  targetId: null,\r\n  fullJid: null,\r\n\r\n  async setPrivateTarget(username) {\r\n    if (!username) {\r\n      this.exitPrivateMode();\r\n      return false;\r\n    }\r\n\r\n    try {\r\n      const userId = await getExactUserIdByName(username);\r\n      if (!userId) return false;\r\n\r\n      this.isPrivateMode = true;\r\n      this.targetUsername = username;\r\n      this.targetId = userId;\r\n      this.fullJid = `${userId}#${username}@jabber.klavogonki.ru/web`;\r\n\r\n      return true;\r\n    } catch (error) {\r\n      console.error('Error setting private target:', error);\r\n      return false;\r\n    }\r\n  },\r\n\r\n  exitPrivateMode() {\r\n    this.isPrivateMode = false;\r\n    this.targetUsername = null;\r\n    this.targetId = null;\r\n    this.fullJid = null;\r\n  }\r\n};\r\n\r\n// Global reference for ESC key handler\r\nlet escKeyHandler = null;\r\n\r\n// Function to handle ESC key press\r\nfunction handleEscKeyPress(event) {\r\n  if (event.key === 'Escape' && privateMessageState.isPrivateMode) {\r\n    exitPrivateMode();\r\n  }\r\n}\r\n\r\n// Toggle private message mode based on input value\r\nasync function handlePrivateMessageInput(inputElement) {\r\n  if (!inputElement) return;\r\n  const input = inputElement.value;\r\n  // Updated regex to include hyphens and other common username special characters\r\n  const privateModeRegex = /^\\/pm\\s+([\\wа-яА-ЯёЁ\\-\\.\\_\\+]+)\\s/;\r\n  const exitPrivateModeRegex = /^\\/exit\\s*$/;\r\n  const match = input.match(privateModeRegex);\r\n  if (match) {\r\n    const username = match[1];\r\n    const success = await privateMessageState.setPrivateTarget(username);\r\n    if (success) {\r\n      enterPrivateMode(username);\r\n      inputElement.value = input.replace(privateModeRegex, ''); // Remove the /pm username part\r\n    } else {\r\n      showChatAlert(`Could not find user \"${username}\"`, { type: 'error', duration: 3000 });\r\n      exitPrivateMode();\r\n    }\r\n  } else if (exitPrivateModeRegex.test(input)) {\r\n    exitPrivateMode();\r\n    inputElement.value = ''; // Clear the input\r\n  }\r\n}\r\n\r\nfunction enterPrivateMode(username) {\r\n  const messageInput = document.getElementById('message-input');\r\n  if (privateMessageState.isPrivateMode && privateMessageState.targetUsername !== username) {\r\n    exitPrivateMode();\r\n  }\r\n\r\n  if (!messageInput.classList.contains('private-mode') || privateMessageState.targetUsername !== username) {\r\n    messageInput.classList.add('private-mode');\r\n    messageInput.placeholder = `PM to ➡ ${username}`;\r\n\r\n    // Create or update exit button\r\n    let exitButton = document.querySelector('.private-mode-exit');\r\n    if (!exitButton) {\r\n      exitButton = document.createElement('span');\r\n      exitButton.className = 'button private-mode-exit';\r\n\r\n      // Add click event to exit private mode\r\n      exitButton.addEventListener('click', () => {\r\n        exitPrivateMode();\r\n        messageInput.focus();\r\n      });\r\n\r\n      // Add the exit button to the UI near the input\r\n      const inputContainer = messageInput.parentElement;\r\n      inputContainer.insertBefore(exitButton, messageInput.nextSibling);\r\n    }\r\n\r\n    // Set default closed lock emoji and title\r\n    exitButton.innerHTML = \"🔒\";\r\n    exitButton.title = \"Exit private mode\";\r\n\r\n    // Change emoji on hover: open lock on mouseenter, closed lock on mouseleave\r\n    exitButton.addEventListener('mouseenter', () => {\r\n      exitButton.innerHTML = \"🔓\";\r\n    });\r\n\r\n    exitButton.addEventListener('mouseleave', () => {\r\n      exitButton.innerHTML = \"🔒\";\r\n    });\r\n\r\n    showChatAlert(`Private chat with ${username} activated`, { type: 'warning', duration: 3000 });\r\n    privateMessageState.isPrivateMode = true;\r\n    privateMessageState.targetUsername = username;\r\n\r\n    // Add ESC key event listener when entering private mode\r\n    if (!escKeyHandler) {\r\n      escKeyHandler = handleEscKeyPress;\r\n      document.addEventListener('keydown', escKeyHandler);\r\n    }\r\n  } else if (privateMessageState.targetUsername === username) {\r\n    messageInput.placeholder = `️PM to ➡ ${username}`;\r\n    showChatAlert(`Private chat with ${username} activated`, { type: 'warning', duration: 3000 });\r\n  }\r\n}\r\n\r\nfunction exitPrivateMode() {\r\n  const messageInput = document.getElementById('message-input');\r\n  if (messageInput.classList.contains('private-mode')) {\r\n    messageInput.classList.remove('private-mode');\r\n    messageInput.placeholder = ''; // Reset placeholder\r\n\r\n    // Remove the exit button\r\n    const exitButton = document.querySelector('.private-mode-exit');\r\n    if (exitButton) exitButton.remove();\r\n\r\n    privateMessageState.exitPrivateMode();\r\n    showChatAlert('Exited private chat mode', { type: 'success', duration: 3000 });\r\n\r\n    // Remove ESC key event listener when exiting private mode\r\n    if (escKeyHandler) {\r\n      document.removeEventListener('keydown', escKeyHandler);\r\n      escKeyHandler = null;\r\n    }\r\n  }\r\n}\r\n\r\n// Handle ESC key to exit private mode\r\nfunction setupPrivateMessageEvents(inputElement) {\r\n  if (!inputElement) return;\r\n\r\n  // Check for private message mode on input changes\r\n  inputElement.addEventListener('input', () => {\r\n    handlePrivateMessageInput(inputElement);\r\n  });\r\n}\r\n\r\n// ==================================================================================================\r\n\r\n// Define available commands with their handlers\r\nconst chatCommands = [\r\n  {\r\n    name: 'reset',\r\n    pattern: /^\\/reset\\s*$/,\r\n    handler: () => {\r\n      (0,_auth_js__WEBPACK_IMPORTED_MODULE_6__.removeChatParams)();\r\n      showChatAlert('Chat settings have been reset. Reloading...', { type: 'info', duration: 1000 });\r\n      return true;\r\n    }\r\n  },\r\n  {\r\n    name: 'colors',\r\n    pattern: /^\\/colors\\s*$/,\r\n    handler: () => {\r\n      (0,_components_chatUsernameColorsPanel_js__WEBPACK_IMPORTED_MODULE_7__.openUsernameColors)();\r\n      showChatAlert('Username color picker has been opened', { type: 'info', duration: 1000 });\r\n      return true;\r\n    }\r\n  },\r\n  {\r\n    name: 'export colors',\r\n    pattern: /^\\/export\\s+colors\\s*$/,\r\n    handler: () => {\r\n      (0,_components_chatUsernameColorsPanel_js__WEBPACK_IMPORTED_MODULE_7__.exportUsernameColors)();\r\n      return true;\r\n    }\r\n  },\r\n  {\r\n    name: 'import colors',\r\n    pattern: /^\\/import\\s+colors\\s*$/,\r\n    handler: () => {\r\n      (0,_components_chatUsernameColorsPanel_js__WEBPACK_IMPORTED_MODULE_7__.importUsernameColors)();\r\n      return true;\r\n    }\r\n  }\r\n];\r\n\r\n// Setup event handlers for commands\r\nfunction setupCommandEvents(inputElement) {\r\n  if (!inputElement) return;\r\n\r\n  // Add input event handler for all commands\r\n  inputElement.addEventListener('input', () => {\r\n    handleCommands(inputElement);\r\n  });\r\n}\r\n\r\n// Handle any command entered in the input field\r\nfunction handleCommands(inputElement) {\r\n  if (!inputElement) return false;\r\n  const input = inputElement.value;\r\n\r\n  // Check each command to see if it matches\r\n  for (const command of chatCommands) {\r\n    if (command.pattern.test(input)) {\r\n      // Execute the command's handler\r\n      const result = command.handler();\r\n\r\n      // Clear the input field if command was successfully handled\r\n      if (result) {\r\n        inputElement.value = '';\r\n      }\r\n\r\n      return result;\r\n    }\r\n  }\r\n\r\n  return false; // Return false if no command matched\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction sleep(ms) {\r\n  return new Promise(resolve => setTimeout(resolve, ms));\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction base64Encode(str) {\r\n  const encoder = new TextEncoder();\r\n  const data = encoder.encode(str);\r\n  return btoa(String.fromCharCode(...data));\r\n}\r\n\r\n// ==================================================================================================\r\n\r\n// Helper function to check if an image exists\r\nfunction checkImageExists(url) {\r\n  return new Promise((resolve) => {\r\n    const img = new Image();\r\n    img.onload = () => resolve(true);\r\n    img.onerror = () => resolve(false);\r\n    img.src = url;\r\n  });\r\n}\r\n\r\n// ==================================================================================================\r\n\r\n// Function to randomize emoji and add shake effect\r\nfunction setupRandomEmojiAttention(emojiButton, frequency) {\r\n  // Get all emoji keys from the emojiKeywords object\r\n  const emojis = Object.keys(_data_emojiData_js__WEBPACK_IMPORTED_MODULE_5__.emojiKeywords);\r\n\r\n  // Original emoji to return to after attention-getting effect\r\n  const originalEmoji = \"🙂\";\r\n\r\n  // Function to select random emoji and apply shake effect\r\n  const showRandomEmoji = () => {\r\n    // Get a random emoji from the collection\r\n    const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)];\r\n\r\n    // Set the random emoji\r\n    emojiButton.innerHTML = randomEmoji;\r\n\r\n    // Apply shake effect\r\n    (0,_data_animations_js__WEBPACK_IMPORTED_MODULE_4__.addShakeEffect)(emojiButton);\r\n\r\n    // Return to original emoji after animation completes\r\n    setTimeout(() => {\r\n      emojiButton.innerHTML = originalEmoji;\r\n    }, 1500);\r\n  };\r\n\r\n  // Set interval to periodically show random emoji\r\n  const intervalId = setInterval(showRandomEmoji, frequency);\r\n\r\n  // Store the intervalId on the element for potential cleanup\r\n  emojiButton.randomEmojiIntervalId = intervalId;\r\n\r\n  return intervalId;\r\n}\r\n\r\n/**\r\n * Generates a random number of milliseconds between the specified minimum and maximum values\r\n * @param {number} minMs - Minimum time in milliseconds\r\n * @param {number} maxMs - Maximum time in milliseconds\r\n * @returns {number} - A random number of milliseconds between minMs and maxMs (inclusive)\r\n */\r\nfunction getRandomInterval(minMs, maxMs) {\r\n  // Ensure min is not greater than max\r\n  if (minMs > maxMs) {\r\n    [minMs, maxMs] = [maxMs, minMs]; // Swap values if min > max\r\n  }\r\n\r\n  // Calculate a random value between min and max (inclusive)\r\n  return Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction addViewportMeta() {\r\n  if (!document.querySelector('meta[name=\"viewport\"]')) {\r\n    const viewportMeta = document.createElement('meta');\r\n    viewportMeta.name = 'viewport';\r\n    viewportMeta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';\r\n    document.head.appendChild(viewportMeta);\r\n    console.log('Viewport meta tag added dynamically');\r\n  }\r\n}\r\n\r\n// ==================================================================================================\r\n\r\n// Instead of getting the elements immediately, we declare module-level variables.\r\nlet chatField = null;\r\nlet messagesContainer = null;\r\nlet lengthPopup = null;\r\n\r\n/**\r\n * Create and append the length popup.\r\n * @param {HTMLElement} container - The container (messagesPanel) to which the popup should be appended.\r\n */\r\nfunction createLengthPopup(container) {\r\n  messagesContainer = container;\r\n  lengthPopup = document.createElement('div');\r\n  lengthPopup.className = 'length-field-popup';\r\n  messagesContainer.appendChild(lengthPopup);\r\n}\r\n\r\n// Create a canvas for text measurement.\r\nconst textMeasurementCanvas = document.createElement('canvas');\r\nconst textMeasurementContext = textMeasurementCanvas.getContext('2d');\r\n\r\nlet isPopupVisible = false;\r\nlet previousLength = 0;\r\nlet hidePopupTimeout;\r\n\r\nfunction updateLengthPopupColor(length) {\r\n  if (!lengthPopup) {\r\n    console.error('lengthPopup is not defined');\r\n    return;\r\n  }\r\n\r\n  let h, s = 100, l = 50;\r\n\r\n  if (length === 0) {\r\n    h = 200; s = 20; l = 50;\r\n  } else if (length <= 90) {\r\n    h = 120;\r\n  } else if (length <= 100) {\r\n    h = 120 - ((length - 90) / 10) * 60;\r\n  } else if (length <= 190) {\r\n    h = 60;\r\n  } else if (length <= 200) {\r\n    h = 60 - ((length - 190) / 10) * 20;\r\n  } else if (length <= 250) {\r\n    h = 40;\r\n  } else if (length <= 300) {\r\n    h = 40 - ((length - 250) / 50) * 40;\r\n  } else {\r\n    h = 0;\r\n  }\r\n\r\n  const textColor = `hsl(${h}, ${s}%, ${l}%)`;\r\n  const backgroundColor = `hsl(${h}, ${s}%, ${Math.max(l - (length > 250 ? 35 : 30), 8)}%)`;\r\n  const borderColor = `hsla(${h}, ${s}%, ${l}%, 0.1)`;\r\n\r\n  lengthPopup.style.setProperty('color', textColor, 'important');\r\n  lengthPopup.style.setProperty('background-color', backgroundColor, 'important');\r\n  lengthPopup.style.setProperty('border', `1px solid ${borderColor}`, 'important');\r\n  lengthPopup.style.setProperty('border-radius', '0.4em', 'important');\r\n}\r\n\r\nfunction updatePopupMetrics(text) {\r\n  if (!chatField) {\r\n    console.error('chatField is not set.');\r\n    return;\r\n  }\r\n  // Get current font from input field.\r\n  const computedStyle = getComputedStyle(chatField);\r\n  textMeasurementContext.font = `${computedStyle.fontWeight} ${computedStyle.fontSize} ${computedStyle.fontFamily}`;\r\n  // Measure text.\r\n  const textWidth = textMeasurementContext.measureText(text).width;\r\n  // Calculate position.\r\n  const newLeft = chatField.offsetLeft + textWidth + 5;\r\n  const maxLeft = chatField.offsetLeft + chatField.offsetWidth - lengthPopup.offsetWidth;\r\n  lengthPopup.style.left = `${Math.min(newLeft, maxLeft)}px`;\r\n}\r\n\r\nconst arrowRightBold = \"➡\"; // Heavy right arrow\r\nconst arrowLeftBold = \"⬅\"; // Heavy left arrow\r\n\r\nfunction updateLengthPopup(length) {\r\n  let displayText =\r\n    length > previousLength ? `${length} ${arrowRightBold}` :\r\n      length < previousLength ? `${arrowLeftBold} ${length}` :\r\n        `${length}`;\r\n\r\n  lengthPopup.textContent = displayText;\r\n  updateLengthPopupColor(length);\r\n  previousLength = length;\r\n}\r\n\r\nfunction togglePopup(show) {\r\n  if (isPopupVisible === show) return;\r\n  lengthPopup.classList.toggle('bounce-in', show);\r\n  lengthPopup.classList.toggle('bounce-out', !show);\r\n  isPopupVisible = show;\r\n  if (!show) setTimeout(() => lengthPopup.classList.remove('bounce-out'), 500);\r\n}\r\n\r\nfunction resetPopup() {\r\n  updateLengthPopup(0);\r\n  Object.assign(lengthPopup.style, { left: '0px', color: 'hsl(200, 20%, 50%)' });\r\n}\r\n\r\nfunction handleInputEvent() {\r\n  clearTimeout(hidePopupTimeout);\r\n  updateLengthPopup(chatField.value.length);\r\n  updatePopupMetrics(chatField.value);\r\n  togglePopup(true);\r\n  hidePopupTimeout = setTimeout(() => togglePopup(false), 1000);\r\n}\r\n\r\nfunction handleKeydownEvent(e) {\r\n  if (e.key !== 'Enter') return;\r\n  resetPopup();\r\n  togglePopup(true);\r\n  hidePopupTimeout = setTimeout(() => togglePopup(false), 1000);\r\n}\r\n\r\n/**\r\n * Initializes chat length popup events.\r\n * @param {HTMLElement} field - The chat input field.\r\n */\r\nfunction initChatLengthPopupEvents(field) {\r\n  chatField = field;\r\n  if (!chatField) {\r\n    console.error('chatField is null');\r\n    return;\r\n  }\r\n  // Only attach event listeners if the popup was created.\r\n  if (!lengthPopup) return;\r\n  chatField.addEventListener('input', handleInputEvent);\r\n  chatField.addEventListener('keydown', handleKeydownEvent);\r\n}\r\n\r\n// ==================================================================================================\r\n\r\n/**\r\n * Converts a given local time to Moscow time (UTC+3) based on the system's timezone.\r\n *\r\n * How it works:\r\n * 1. Gets the system's local timezone offset in minutes (positive if behind UTC).\r\n * 2. Converts the local offset to total minutes from UTC.\r\n * 3. Defines Moscow's fixed offset as UTC+3 (180 minutes).\r\n * 4. Calculates the difference between Moscow's offset and the local offset.\r\n * 5. Parses the input time and converts it into total minutes since midnight.\r\n * 6. Adjusts the time by the calculated difference.\r\n * 7. Ensures the result stays within the 24-hour format (wrap-around handling).\r\n * 8. Converts the result back to HH:MM:SS format and returns it.\r\n *\r\n * @param {string} time - The local time in \"HH:MM:SS\" format.\r\n * @returns {string} - The converted time in Moscow time (HH:MM:SS).\r\n */\r\nfunction calibrateToMoscowTime(time) {\r\n  // Get local timezone offset in minutes (positive if local is behind UTC)\r\n  const localOffsetMinutes = new Date().getTimezoneOffset();\r\n\r\n  // Convert local offset to total minutes from UTC (local time = UTC + localTotalOffset)\r\n  const localTotalOffset = -localOffsetMinutes;\r\n\r\n  // Moscow is UTC+3 (180 minutes)\r\n  const moscowOffset = 3 * 60; // 180 minutes\r\n\r\n  // Calculate the adjustment needed: Moscow offset - local offset\r\n  const diffMinutes = moscowOffset - localTotalOffset;\r\n\r\n  // Parse input time\r\n  const [hours, minutes, seconds] = time.split(':').map(Number);\r\n\r\n  // Convert input time to total minutes since 00:00\r\n  const totalInputMinutes = hours * 60 + minutes;\r\n\r\n  // Adjust by diff and wrap within a single day (1440 minutes)\r\n  let adjustedMinutes = totalInputMinutes + diffMinutes;\r\n  adjustedMinutes = ((adjustedMinutes % 1440) + 1440) % 1440; // Ensure positive\r\n\r\n  // Convert back to hours and minutes\r\n  const adjustedHours = Math.floor(adjustedMinutes / 60);\r\n  const adjustedMins = adjustedMinutes % 60;\r\n\r\n  // Format the result with original seconds\r\n  return `${adjustedHours.toString().padStart(2, '0')}:` +\r\n    `${adjustedMins.toString().padStart(2, '0')}:` +\r\n    `${seconds.toString().padStart(2, '0')}`;\r\n}\r\n\r\n// ==================================================================================================\r\n\r\n// Removes newlines and excess whitespace from XML strings to create a compact single-line format while preserving content.\r\nfunction compactXML(xmlString) {\r\n  // Remove all newlines and trim excess whitespace to single spaces\r\n  return xmlString\r\n    .replace(/\\n/g, '')      // Remove all newlines\r\n    .replace(/\\s+/g, ' ')    // Replace multiple spaces/tabs with a single space\r\n    .replace(/>\\s+</g, '><') // Remove spaces between tags\r\n    .trim();                 // Remove leading/trailing whitespace\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction checkIsMobile() {\r\n  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||\r\n    ('ontouchstart' in window);\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction toggleChatVisibility() {\r\n  const chatContainer = document.getElementById('app-chat-container');\r\n  const toggleButton = document.querySelector('.chat-toggle-button');\r\n  if (!chatContainer) return;\r\n\r\n  // Prevent toggling visibility if chat is maximized\r\n  if (chatContainer.classList.contains('maximized')) {\r\n    showChatAlert('Chat is currently maximized', {\r\n      type: 'warning',\r\n      duration: 1000\r\n    });\r\n    return;\r\n  }\r\n\r\n  const chatState = JSON.parse(localStorage.getItem('chatState')) || {};\r\n  const isFloating = chatState.floating || false;\r\n\r\n  if (isFloating) {\r\n    const isBecomingVisible = chatContainer.style.opacity === '0';\r\n    chatContainer.style.opacity = isBecomingVisible ? '1' : '0';\r\n    setTimeout(() => {\r\n      chatContainer.style.display = isBecomingVisible ? 'flex' : 'none';\r\n      toggleButton.innerHTML = isBecomingVisible ? _data_icons_js__WEBPACK_IMPORTED_MODULE_3__.closeSVG : _data_icons_js__WEBPACK_IMPORTED_MODULE_3__.openSVG;\r\n      saveChatState({\r\n        ...chatState,\r\n        isVisible: isBecomingVisible\r\n      });\r\n      if (isBecomingVisible) {\r\n        focusTextInput(); // Focus input after chat becomes visible\r\n      }\r\n    }, 300);\r\n  } else {\r\n    const isCurrentlyVisible = chatContainer.classList.contains('visible-chat');\r\n    const isBecomingVisible = !isCurrentlyVisible;\r\n    chatContainer.classList.remove('visible-chat', 'hidden-chat');\r\n    chatContainer.classList.add(isBecomingVisible ? 'visible-chat' : 'hidden-chat');\r\n    toggleButton.innerHTML = isBecomingVisible ? _data_icons_js__WEBPACK_IMPORTED_MODULE_3__.closeSVG : _data_icons_js__WEBPACK_IMPORTED_MODULE_3__.openSVG;\r\n    saveChatState({\r\n      ...chatState,\r\n      isVisible: isBecomingVisible\r\n    });\r\n    if (isBecomingVisible) {\r\n      focusTextInput(); // Focus input immediately when shown\r\n    }\r\n  }\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nfunction addChatToggleFeature() {\r\n  const chatContainer = document.getElementById('app-chat-container');\r\n  const closeButton = document.getElementById('chat-close-btn');\r\n  const draggableHeader = document.getElementById('chat-header');\r\n  if (!chatContainer) return;\r\n\r\n  // Restore initial visibility from saved state\r\n  const chatState = JSON.parse(localStorage.getItem('chatState')) || {};\r\n  const isFloating = chatState.floating || false;\r\n  const isVisible = chatState.isVisible !== false;\r\n\r\n  if (isFloating) {\r\n    chatContainer.style.display = isVisible ? 'flex' : 'none';\r\n    chatContainer.style.opacity = isVisible ? '1' : '0';\r\n  } else {\r\n    chatContainer.classList.remove('visible-chat', 'hidden-chat');\r\n    chatContainer.classList.add(isVisible ? 'visible-chat' : 'hidden-chat');\r\n  }\r\n\r\n  // Keyboard shortcuts\r\n  document.addEventListener('keydown', (e) => {\r\n    if (e.ctrlKey && e.shiftKey && e.code === 'Space') {\r\n      e.preventDefault();\r\n      toggleChatMaximize();\r\n    } else if (e.ctrlKey && e.code === 'Space') {\r\n      e.preventDefault();\r\n      toggleChatVisibility();\r\n    }\r\n  });\r\n\r\n  if (closeButton) {\r\n    closeButton.addEventListener('click', toggleChatVisibility);\r\n  }\r\n\r\n  if (draggableHeader) {\r\n    draggableHeader.addEventListener('dblclick', toggleChatVisibility);\r\n  }\r\n}\r\n\r\n// ==================================================================================================\r\n\r\nlet originalChatState = null;\r\n\r\nfunction toggleChatMaximize() {\r\n  const chat = document.getElementById('app-chat-container');\r\n  const maximizeButton = document.querySelector('.chat-maximize-button');\r\n  if (!chat) return;\r\n  if (!chat.classList.contains('maximized')) {\r\n    const hasVisibilityClass = !chat.classList.contains('visible-chat') && !chat.classList.contains('hidden-chat');\r\n    originalChatState = getChatState();\r\n    const calculateHeight = () => `${Math.floor(window.innerHeight * 0.9)}px`;\r\n    chat.style.cssText = `\r\n      width: 100vw !important;\r\n      height: ${calculateHeight()} !important;\r\n      max-width: 100vw !important;\r\n      min-width: 100vw !important;\r\n      position: fixed !important;\r\n      bottom: 0 !important;\r\n      left: 0 !important;\r\n      right: 0 !important;\r\n      top: auto !important;\r\n      margin: 0 !important;\r\n      transform: none !important;\r\n    `;\r\n    if (hasVisibilityClass) {\r\n      chat.classList.remove('visible-chat', 'hidden-chat');\r\n    }\r\n    chat.classList.add('maximized');\r\n    maximizeButton.classList.add('maximized');\r\n    maximizeButton.innerHTML = _data_icons_js__WEBPACK_IMPORTED_MODULE_3__.collapseSVG;\r\n    const resizeHandler = () => {\r\n      chat.style.height = calculateHeight();\r\n      chat.style.bottom = '0';\r\n      chat.style.top = 'auto';\r\n    };\r\n    window.addEventListener('resize', resizeHandler);\r\n    chat.maximizeResizeHandler = resizeHandler;\r\n    handleElementsBehavior();\r\n    focusTextInput();\r\n    restoreFontSize();\r\n  } else {\r\n    const container = document.getElementById('messages-panel');\r\n    const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;\r\n    const shouldScrollToBottom = distanceFromBottom <= 300;\r\n    if (chat.maximizeResizeHandler) {\r\n      window.removeEventListener('resize', chat.maximizeResizeHandler);\r\n      delete chat.maximizeResizeHandler;\r\n    }\r\n    if (originalChatState) {\r\n      chat.style.width = `${originalChatState.width}px`;\r\n      chat.style.height = `${originalChatState.height}px`;\r\n      chat.style.left = `${originalChatState.left}px`;\r\n      chat.style.maxWidth = '';\r\n      chat.style.minWidth = '';\r\n      chat.style.position = 'fixed';\r\n      chat.style.right = '';\r\n      chat.style.margin = '';\r\n      chat.style.transform = '';\r\n      chat.style.top = 'auto';\r\n      if (originalChatState.floating) {\r\n        const viewportHeight = window.innerHeight;\r\n        const proposedTop = originalChatState.top;\r\n        if (proposedTop + originalChatState.height <= viewportHeight) {\r\n          chat.style.top = `${proposedTop}px`;\r\n        } else {\r\n          chat.style.bottom = '0';\r\n          chat.style.top = 'auto';\r\n        }\r\n      } else {\r\n        chat.style.bottom = '0';\r\n        chat.style.top = '';\r\n      }\r\n      const currentState = getChatState();\r\n      const newState = {\r\n        ...currentState,\r\n        width: originalChatState.width,\r\n        height: originalChatState.height,\r\n        left: originalChatState.left,\r\n        top: originalChatState.top,\r\n        floating: originalChatState.floating,\r\n        isVisible: originalChatState.isVisible,\r\n      };\r\n      saveChatState(newState);\r\n    }\r\n    chat.classList.remove('maximized');\r\n    maximizeButton.classList.remove('maximized');\r\n    maximizeButton.innerHTML = _data_icons_js__WEBPACK_IMPORTED_MODULE_3__.expandSVG;\r\n    requestAnimationFrame(() => {\r\n      handleElementsBehavior();\r\n      if (shouldScrollToBottom) {\r\n        container.scrollTop = container.scrollHeight;\r\n      }\r\n      focusTextInput();\r\n      restoreFontSize();\r\n    });\r\n  }\r\n}\r\n\r\n// ==================================================================================================\r\n\r\n// Add this function to handle mobile/touch devices\r\nfunction handleMobileLayout(messagesPanel, inputContainer) {\r\n  const isMobile = checkIsMobile();\r\n  if (isMobile) {\r\n    // Initial setup - fixed positioning for input container\r\n    inputContainer.style.position = 'fixed';\r\n    inputContainer.style.bottom = '0';\r\n    inputContainer.style.left = '0';\r\n    inputContainer.style.right = '0';\r\n    inputContainer.style.borderBottom = '1px solid #333';\r\n    inputContainer.style.zIndex = '100';\r\n    // Set margin for messages panel\r\n    messagesPanel.style.marginBottom = `${inputContainer.offsetHeight}px`;\r\n    // Add styles for mobile view\r\n    const globalMobileStyles = document.createElement('style');\r\n    globalMobileStyles.classList.add('global-mobile-styles');\r\n    globalMobileStyles.textContent = `\r\n      #app-chat-container .emoji-panel {\r\n        transform: translate(-50%, 0%) !important;\r\n        height: 60vh !important;\r\n        top: 1em !important;\r\n        left: 50% !important;\r\n        right: unset !important;\r\n      }\r\n      #app-chat-container .reveal-userlist-btn {\r\n        transform: none !important; \r\n      }\r\n      #app-chat-container .user-list-container {\r\n        top: 1em !important;\r\n        height: fit-content !important;\r\n        max-height: 70vh !important;\r\n        border-top: 1px solid #333 !important;\r\n        border-bottom: 1px solid #333 !important;\r\n        border-radius: 0.5em 0 0 0.5em !important;\r\n      }\r\n      #app-chat-container .toggle-button {\r\n        border: none !important;\r\n        top: 0 !important;\r\n        right: 0 !important;\r\n        border-radius: 0.2em !important;\r\n        margin: 1em !important;\r\n        box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1) !important;\r\n      }\r\n      /* Add transition for smoother position changes */\r\n      #app-chat-container .input-container, \r\n      #app-chat-container .length-field-popup {\r\n        transition: bottom 0.2s ease-out;\r\n      }\r\n      /* Position the length-field-popup relative to bottom */\r\n      #app-chat-container .length-field-popup {\r\n        position: fixed !important;\r\n      }\r\n    `;\r\n    document.head.appendChild(globalMobileStyles);\r\n\r\n    // Function to update the reveal button's vertical position\r\n    const updateRevealButtonPosition = () => {\r\n      const revealButton = document.querySelector('#app-chat-container .reveal-userlist-btn');\r\n      const container = document.getElementById('app-chat-container');\r\n      if (revealButton && container) {\r\n        // Use visualViewport height instead of innerHeight when available\r\n        const viewportHeight = window.visualViewport ? window.visualViewport.height : window.innerHeight;\r\n        const buttonHeight = revealButton.offsetHeight || 0;\r\n\r\n        // Calculate the middle of the visible viewport\r\n        const middle = viewportHeight / 2 - buttonHeight / 2;\r\n\r\n        // Adjust for visualViewport offsetTop (scroll position) if available\r\n        const offsetTop = window.visualViewport ? window.visualViewport.offsetTop : 0;\r\n\r\n        // Position the button at the middle of the visible screen area\r\n        revealButton.style.setProperty('top', `${middle + offsetTop}px`, 'important');\r\n      }\r\n    };\r\n\r\n    // Function to update the length-field-popup position\r\n    const updateLengthFieldPosition = () => {\r\n      const lengthField = document.querySelector('#app-chat-container .length-field-popup');\r\n      if (lengthField && inputContainer) {\r\n        // Position the length field above the input container\r\n        const inputHeight = inputContainer.offsetHeight;\r\n        lengthField.style.setProperty('bottom', `${inputHeight}px`, 'important');\r\n      }\r\n    };\r\n\r\n    // Initial updates\r\n    updateRevealButtonPosition();\r\n    updateLengthFieldPosition();\r\n\r\n    // Use Visual Viewport API for keyboard detection and correct positioning\r\n    if (window.visualViewport) {\r\n      window.visualViewport.addEventListener('resize', () => {\r\n        // Calculate the bottom offset taking into account the viewport offset when scrolling\r\n        const bottomOffset = window.innerHeight - window.visualViewport.height - window.visualViewport.offsetTop;\r\n\r\n        // Update input container position\r\n        inputContainer.style.bottom = `${bottomOffset}px`;\r\n\r\n        // Update length field popup position\r\n        const lengthField = document.querySelector('#app-chat-container .length-field-popup');\r\n        if (lengthField) {\r\n          lengthField.style.bottom = `${bottomOffset + inputContainer.offsetHeight}px`;\r\n        }\r\n\r\n        // Recalculate the messages panel bottom margin\r\n        messagesPanel.style.marginBottom = `${inputContainer.offsetHeight}px`;\r\n\r\n        // Update the reveal button position based on the new input container position\r\n        updateRevealButtonPosition();\r\n      });\r\n    }\r\n  }\r\n}\r\n\r\n// ==================================================================================================\r\n\r\n// Helper function to generate a random string\r\nfunction generateRandomString() {\r\n  return Math.random().toString(36).slice(2);\r\n}\r\n\r\n// ==================================================================================================\r\n\r\n/**\r\n * Creates an HR-like separator with an emoji icon.\r\n * @returns {HTMLElement} The separator element.\r\n */\r\nfunction createNewMessagesSeparator() {\r\n  const separator = document.createElement('div');\r\n  separator.className = 'new-messages-separator';\r\n\r\n  const hr = document.createElement('hr');\r\n  hr.className = 'separator-line';\r\n\r\n  // Use an emoji icon (feel free to change it)\r\n  const iconContainer = document.createElement('div');\r\n  iconContainer.className = 'separator-icon';\r\n  iconContainer.textContent = '🔥';\r\n\r\n  separator.appendChild(hr);\r\n  separator.appendChild(iconContainer);\r\n\r\n  return separator;\r\n}\r\n\r\n/**\r\n * Removes the separator if it exists from the provided container.\r\n * @param {HTMLElement} panel - The container element to check for the separator.\r\n */\r\nfunction removeNewMessagesSeparator(panel) {\r\n  const separator = panel.querySelector('.new-messages-separator');\r\n  if (separator) {\r\n    separator.remove();\r\n  }\r\n}\r\n\r\n// ==================================================================================================\r\n\r\n// Debounce helper\r\nconst debounce = (func, wait) => {\r\n  let timeout;\r\n  return function (...args) {\r\n    clearTimeout(timeout);\r\n    timeout = setTimeout(() => func.apply(this, args), wait);\r\n  };\r\n};\r\n\r\n// ===================================================================================================\n\n//# sourceURL=webpack://tampermonkey-script/./src/helpers/helpers.js?");

/***/ }),

/***/ "./src/helpers/parser.js":
/*!*******************************!*\
  !*** ./src/helpers/parser.js ***!
  \*******************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   parseMarkdown: () => (/* binding */ parseMarkdown),\n/* harmony export */   parseMessageText: () => (/* binding */ parseMessageText)\n/* harmony export */ });\n/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./helpers.js */ \"./src/helpers/helpers.js\");\n\r\n\r\nconst parseMessageText = text => {\r\n  // First, apply markdown transformations\r\n  text = parseMarkdown(text);\r\n\r\n  let i = 0, urls = [];\r\n  // Extract URLs and replace them with placeholders\r\n  text = text.replace(/(\\b(https?|ftp):\\/\\/[-A-Z0-9+&@#\\/%?=~|!:,.;()_]*[-A-Z0-9+&@#\\/%=~|()_])/ig, m => {\r\n    urls.push(m);\r\n    return `___URL${i++}___`;\r\n  });\r\n\r\n  // Replace smilies and adjust emoji presentation\r\n  text = text\r\n    .replace(/:(\\w+):/g, (_, e) => `<img src=\"https://klavogonki.ru/img/smilies/${e}.gif\" alt=\"${e}\" />`)\r\n    .replace(/(\\p{Emoji_Presentation}|\\p{Emoji}\\uFE0F)/gu, '<span class=\"emoji-adjuster\">$&</span>');\r\n\r\n  // Replace placeholders with anchor tags after markdown processing\r\n  urls.forEach((url, idx) => {\r\n    if ((0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.isEncodedURL)(url)) {\r\n      const decodedURL = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.decodeURL)(url);\r\n      text = text.replace(\r\n        `___URL${idx}___`,\r\n        `<a class=\"processed-link decoded-link\" href=\"${url}\" target=\"_blank\">${decodedURL}</a>`\r\n      );\r\n    } else {\r\n      text = text.replace(\r\n        `___URL${idx}___`,\r\n        `<a class=\"processed-link\" href=\"${url}\" target=\"_blank\">${url}</a>`\r\n      );\r\n    }\r\n  });\r\n\r\n  return text;\r\n}\r\n\r\n// Basic markdown support function with additional CSS classes\r\nconst parseMarkdown = text => {\r\n  // Convert markdown headings to HTML headings with extra class names\r\n  text = text.replace(/^######\\s+(.*)$/gim, '<h6 class=\"md-heading md-h6\">$1</h6>');\r\n  text = text.replace(/^#####\\s+(.*)$/gim, '<h5 class=\"md-heading md-h5\">$1</h5>');\r\n  text = text.replace(/^####\\s+(.*)$/gim, '<h4 class=\"md-heading md-h4\">$1</h4>');\r\n  text = text.replace(/^###\\s+(.*)$/gim, '<h3 class=\"md-heading md-h3\">$1</h3>');\r\n  text = text.replace(/^##\\s+(.*)$/gim, '<h2 class=\"md-heading md-h2\">$1</h2>');\r\n  text = text.replace(/^#\\s+(.*)$/gim, '<h1 class=\"md-heading md-h1\">$1</h1>');\r\n\r\n  // Convert inline code enclosed in backticks\r\n  text = text.replace(/`([^`]+)`/g, '<code class=\"md-code\">$1</code>');\r\n\r\n  // Convert bold text (using ** only)\r\n  text = text.replace(/\\*\\*(.+?)\\*\\*/g, '<strong class=\"md-bold\">$1</strong>');\r\n\r\n  // Convert italic text (using __ only)\r\n  text = text.replace(/__(.+?)__/g, '<em class=\"md-italic\">$1</em>');\r\n\r\n  // Convert markdown strikethrough using ~~text~~\r\n  text = text.replace(/~~(.+?)~~/g, '<del class=\"md-strikethrough\">$1</del>');\r\n\r\n  return text;\r\n};\r\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/helpers/parser.js?");

/***/ }),

/***/ "./src/managers/messageManager.js":
/*!****************************************!*\
  !*** ./src/managers/messageManager.js ***!
  \****************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (/* binding */ MessageManager)\n/* harmony export */ });\n/* harmony import */ var _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../helpers/helpers.js */ \"./src/helpers/helpers.js\");\n/* harmony import */ var _chat_chatMessagesRemover_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../chat/chatMessagesRemover.js */ \"./src/chat/chatMessagesRemover.js\");\n/* harmony import */ var _helpers_parser_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../helpers/parser.js */ \"./src/helpers/parser.js\");\n/* harmony import */ var _helpers_chatUsernameColors_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../helpers/chatUsernameColors.js */ \"./src/helpers/chatUsernameColors.js\");\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nclass MessageManager {\r\n  constructor(panelId = 'messages-panel', currentUsername = '') {\r\n    this.panel = document.getElementById(panelId);\r\n    this.messageMap = new Map();\r\n    // renderedMessageIds will be maintained in sync with the DOM\r\n    this.renderedMessageIds = new Set();\r\n    this.currentUsername = currentUsername;\r\n    this.maxMessages = 30;\r\n    this.initialLoadComplete = false;\r\n    this.chatRemover = new _chat_chatMessagesRemover_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\r\n    this.messageInput = document.getElementById('message-input');\r\n    this.newSeparatorAdded = false; // flag for separator insertion\r\n    this._delegatedClickAttached = false;\r\n    this.separatorTimer = null;\r\n\r\n    // Listen for tab visibility changes to handle separator removal\r\n    document.addEventListener(\"visibilitychange\", () => {\r\n      if (this.separatorTimer) {\r\n        clearTimeout(this.separatorTimer);\r\n        this.separatorTimer = null;\r\n      }\r\n\r\n      if (document.hidden) {\r\n        (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.removeNewMessagesSeparator)(this.panel);\r\n        this.newSeparatorAdded = false;\r\n      } else if (this.newSeparatorAdded) {\r\n        this.separatorTimer = setTimeout(() => {\r\n          (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.removeNewMessagesSeparator)(this.panel);\r\n          this.newSeparatorAdded = false;\r\n        }, 15000);\r\n      }\r\n    });\r\n  }\r\n\r\n  // Updated unique ID generator to include the timestamp\r\n  generateUniqueId(type, timestamp, username, text) {\r\n    const time = timestamp || new Date().toLocaleTimeString('en-GB', { hour12: false });\r\n    if (type === 'private') {\r\n      return `private-${(0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.generateRandomString)()}`;\r\n    }\r\n    return `${time}-${username}-${text}`;\r\n  }\r\n\r\n  addMessage(messageObj) {\r\n    this.messageMap.set(messageObj.id, messageObj);\r\n    this.trimMessages();\r\n    return true;\r\n  }\r\n\r\n  trimMessages() {\r\n    while (this.messageMap.size > this.maxMessages) {\r\n      const oldestKey = this.messageMap.keys().next().value;\r\n      this.messageMap.delete(oldestKey);\r\n      this.renderedMessageIds.delete(oldestKey);\r\n    }\r\n  }\r\n\r\n  processMessages(xmlResponse) {\r\n    if (typeof xmlResponse !== 'string' || !xmlResponse) return;\r\n    const doc = new DOMParser().parseFromString(xmlResponse, \"text/xml\");\r\n    const messageElements = doc.getElementsByTagName(\"message\");\r\n    let newMessagesAdded = false;\r\n\r\n    Array.from(messageElements).forEach(msg => {\r\n      const bodyNode = msg.getElementsByTagName(\"body\")[0];\r\n      if (!bodyNode || !bodyNode.textContent) return;\r\n      const text = bodyNode.textContent.trim();\r\n      if (text === \"This room is not anonymous\") return;\r\n\r\n      const fromAttr = msg.getAttribute(\"from\");\r\n      const from = fromAttr\r\n        ? fromAttr.split('#')[1]?.split('@')[0] || \"unknown\"\r\n        : \"unknown\";\r\n      const cleanFrom = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.parseUsername)(from);\r\n\r\n      const toAttr = msg.getAttribute(\"to\");\r\n      const typeAttr = msg.getAttribute(\"type\");\r\n      const isPrivate = typeAttr === 'chat';\r\n      let recipient = null;\r\n      if (isPrivate && toAttr) {\r\n        recipient = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.parseUsername)(toAttr.split('#')[1]?.split('@')[0] || toAttr);\r\n      }\r\n\r\n      // Extract timestamp from delay element\r\n      let timestamp = null;\r\n      const delayEl = msg.getElementsByTagName(\"delay\")[0];\r\n      if (delayEl && delayEl.getAttribute(\"stamp\")) {\r\n        const stampStr = delayEl.getAttribute(\"stamp\");\r\n        try {\r\n          const stampDate = new Date(stampStr);\r\n          // Always use the server timestamp here.\r\n          timestamp = stampDate.toLocaleTimeString('en-GB', { hour12: false });\r\n        } catch (e) {\r\n          console.error(\"Error parsing timestamp:\", e);\r\n        }\r\n      }\r\n      if (!timestamp) {\r\n        // Only fall back to local time if there's no server timestamp at all.\r\n        timestamp = new Date().toLocaleTimeString('en-GB', { hour12: false });\r\n      }\r\n\r\n      // Prevent loading my own messages if already loaded\r\n      if (cleanFrom === this.currentUsername && this.initialLoadComplete) return;\r\n\r\n      const isDuplicate = Array.from(this.messageMap.values()).some(existingMsg =>\r\n        existingMsg.from === cleanFrom &&\r\n        existingMsg.text === text\r\n      );\r\n\r\n      if (!isDuplicate) {\r\n        const uniqueId = this.generateUniqueId(\r\n          isPrivate ? 'private' : 'public',\r\n          timestamp, // server timestamp\r\n          cleanFrom,\r\n          text\r\n        );\r\n        const messageObj = {\r\n          id: uniqueId,\r\n          from: cleanFrom,\r\n          text,\r\n          isPrivate,\r\n          recipient,\r\n          pending: false,\r\n          timestamp\r\n        };\r\n\r\n        if (this.addMessage(messageObj)) {\r\n          newMessagesAdded = true;\r\n        }\r\n      }\r\n    });\r\n\r\n    if (newMessagesAdded) {\r\n      this.updatePanel();\r\n    }\r\n  }\r\n\r\n  addSentMessage(text, options = {}) {\r\n    const isPrivate = options.isPrivate || false;\r\n    const currentTime = new Date().toLocaleTimeString('en-GB', { hour12: false });\r\n    const uniqueId = this.generateUniqueId(\r\n      isPrivate ? 'private' : 'public',\r\n      currentTime, // local timestamp\r\n      this.currentUsername,\r\n      text\r\n    );\r\n    const messageObj = {\r\n      id: uniqueId,\r\n      from: this.currentUsername,\r\n      text,\r\n      isPrivate,\r\n      recipient: options.recipient || null,\r\n      pending: options.pending || false,\r\n      timestamp: currentTime\r\n    };\r\n    if (this.addMessage(messageObj)) {\r\n      this.updatePanel();\r\n    }\r\n    return uniqueId;\r\n  }\r\n\r\n  updatePendingStatus(messageId, pendingStatus) {\r\n    const msg = this.messageMap.get(messageId);\r\n    if (msg) {\r\n      msg.pending = pendingStatus;\r\n      this.updatePanel();\r\n    }\r\n  }\r\n\r\n  updatePanel() {\r\n    if (!this.panel) return;\r\n    // Compute the set of IDs already rendered in the DOM.\r\n    const domRenderedIds = new Set(\r\n      Array.from(this.panel.querySelectorAll('.message')).map(el => el.getAttribute('data-message-id'))\r\n    );\r\n    // Synchronize the in‑memory set with the DOM.\r\n    this.renderedMessageIds = domRenderedIds;\r\n\r\n    // Create a single fragment for new messages.\r\n    const fragment = document.createDocumentFragment();\r\n    let mentionDetected = false;\r\n\r\n    // Iterate over our messages from the messageMap.\r\n    this.messageMap.forEach((msg, id) => {\r\n      if (!this.renderedMessageIds.has(id)) {\r\n        const formattedTime = msg.timestamp || new Date().toLocaleTimeString('en-GB', { hour12: false });\r\n        const normalizedUsername = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.parseUsername)(msg.from);\r\n        const usernameColor = _helpers_chatUsernameColors_js__WEBPACK_IMPORTED_MODULE_3__.usernameColors.getColor(normalizedUsername);\r\n        const messageEl = document.createElement('div');\r\n        messageEl.className = 'message';\r\n        messageEl.setAttribute('data-message-id', id);\r\n\r\n        // Add classname 'banned' for Клавобот\r\n        if (msg.from === 'Клавобот') {\r\n          messageEl.classList.add('banned');\r\n          if (this.initialLoadComplete) {\r\n            (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.playAudio)(_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.banned);\r\n          }\r\n        }\r\n\r\n        if (msg.isPrivate) {\r\n          messageEl.classList.add('private-message');\r\n          messageEl.classList.add(msg.from === this.currentUsername ? 'sent' : 'received');\r\n          if (msg.recipient) {\r\n            messageEl.setAttribute('data-recipient', msg.recipient);\r\n          }\r\n        }\r\n\r\n        if (msg.text.startsWith('/me ')) {\r\n          messageEl.classList.add('system');\r\n          msg.text = `${msg.from} ${msg.text.substring(msg.text.indexOf(' ') + 1)}`;\r\n        }\r\n        if (msg.isSystem) {\r\n          messageEl.classList.add('system');\r\n        }\r\n\r\n        // Build message info.\r\n        const messageInfoEl = document.createElement('div');\r\n        messageInfoEl.className = 'message-info';\r\n        let usernameDisplay = msg.from;\r\n        if (msg.isPrivate) {\r\n          usernameDisplay = msg.from === this.currentUsername && msg.recipient\r\n            ? `→ ${msg.recipient}`\r\n            : `${msg.from} →`;\r\n        }\r\n        messageInfoEl.innerHTML = `\r\n          <span class=\"time\">${formattedTime}</span>\r\n          <span class=\"username\" style=\"color: ${usernameColor}\">${usernameDisplay}</span>\r\n        `;\r\n\r\n        // Build message text.\r\n        const messageTextEl = document.createElement('div');\r\n        messageTextEl.className = 'message-text';\r\n        messageTextEl.innerHTML = (0,_helpers_parser_js__WEBPACK_IMPORTED_MODULE_2__.parseMessageText)(msg.text);\r\n        if (msg.pending) {\r\n          const pendingIconEl = document.createElement('span');\r\n          pendingIconEl.className = 'pending-emoji';\r\n          pendingIconEl.textContent = ' ⏱️';\r\n          messageTextEl.appendChild(pendingIconEl);\r\n        }\r\n\r\n        messageEl.appendChild(messageInfoEl);\r\n        messageEl.appendChild(messageTextEl);\r\n        fragment.appendChild(messageEl);\r\n\r\n        // Mark this message as rendered.\r\n        this.renderedMessageIds.add(id);\r\n\r\n        if (this.currentUsername && msg.text.includes(this.currentUsername)) {\r\n          mentionDetected = true;\r\n        }\r\n      }\r\n    });\r\n\r\n    // If the tab is inactive and new messages have arrived, insert the separator.\r\n    if (document.hidden && this.initialLoadComplete && fragment.childNodes.length > 0 && !this.newSeparatorAdded) {\r\n      const separator = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.createNewMessagesSeparator)();\r\n      fragment.insertBefore(separator, fragment.firstChild); // Insert at the beginning of the fragment\r\n      this.newSeparatorAdded = true;\r\n    }\r\n\r\n    // Append all new messages in one DOM operation.\r\n    this.panel.appendChild(fragment);\r\n    this.addDelegatedClickListeners();\r\n    (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.highlightMentionWords)([this.currentUsername]);\r\n\r\n    requestAnimationFrame(() => {\r\n      (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.scrollToBottom)(250);\r\n    });\r\n\r\n    if (this.initialLoadComplete && mentionDetected) {\r\n      (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.playAudio)(_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.notification);\r\n    }\r\n    this.initialLoadComplete = true;\r\n\r\n    if (this.chatRemover) {\r\n      this.chatRemover.updateDeletedMessages();\r\n      this.chatRemover.renderToggle();\r\n    }\r\n  }\r\n\r\n  addDelegatedClickListeners() {\r\n    if (!this.panel._delegatedClickAttached) {\r\n      // Track click count and timing for double-click detection\r\n      let lastClickTime = 0;\r\n      let lastClickUsername = '';\r\n\r\n      this.panel.addEventListener(\"click\", async (event) => {\r\n        const usernameEl = event.target.closest('.username');\r\n        if (usernameEl && this.panel.contains(usernameEl)) {\r\n          const usernameText = usernameEl.textContent.trim();\r\n          let selectedUsername = usernameText;\r\n\r\n          if (selectedUsername.includes('→')) {\r\n            if (selectedUsername.startsWith('→')) {\r\n              selectedUsername = selectedUsername.replace('→', '').trim();\r\n            } else {\r\n              selectedUsername = selectedUsername.split('→')[0].trim();\r\n            }\r\n          }\r\n\r\n          // Handle Shift+Click to navigate to user profile\r\n          if (event.shiftKey) {\r\n            // Get the stored username IDs object from sessionStorage\r\n            let usernameIds = {};\r\n            const storedIds = sessionStorage.getItem('usernameIds');\r\n            if (storedIds) {\r\n              try {\r\n                usernameIds = JSON.parse(storedIds);\r\n              } catch (e) {\r\n                console.error('Error parsing stored username IDs:', e);\r\n                usernameIds = {};\r\n              }\r\n            }\r\n\r\n            // Check if the username already has a stored ID\r\n            let userId = usernameIds[selectedUsername];\r\n\r\n            if (!userId) {\r\n              // If not cached, fetch the user ID and store it\r\n              userId = await (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.getExactUserIdByName)(selectedUsername);\r\n              if (userId) {\r\n                // Update the object and save it back to sessionStorage\r\n                usernameIds[selectedUsername] = userId;\r\n                sessionStorage.setItem('usernameIds', JSON.stringify(usernameIds));\r\n              }\r\n            }\r\n\r\n            if (userId) {\r\n              const navigateToProfileUrl = `https://klavogonki.ru/u/#/${userId}/`;\r\n              window.location.href = navigateToProfileUrl;\r\n            }\r\n            return;\r\n          }\r\n\r\n          // Original Ctrl+Click behavior for private messaging\r\n          if (event.ctrlKey) {\r\n            this.messageInput.value = `/pm ${selectedUsername} `;\r\n            (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.handlePrivateMessageInput)(this.messageInput);\r\n          } else {\r\n            // Detect double-click\r\n            const now = Date.now();\r\n            const isDoubleClick = (now - lastClickTime < 300) && (lastClickUsername === selectedUsername);\r\n\r\n            if (isDoubleClick) {\r\n              // Double-click: Replace entire input with username\r\n              this.messageInput.value = `${selectedUsername}, `;\r\n            } else {\r\n              // Single-click: Append username\r\n              const appendUsername = `${selectedUsername}, `;\r\n              if (!this.messageInput.value.includes(appendUsername)) {\r\n                this.messageInput.value += appendUsername;\r\n              }\r\n            }\r\n\r\n            // Update tracking variables\r\n            lastClickTime = now;\r\n            lastClickUsername = selectedUsername;\r\n          }\r\n          this.messageInput.focus();\r\n        }\r\n\r\n        // Original time element click behavior\r\n        const timeEl = event.target.closest('.time');\r\n        if (timeEl && this.panel.contains(timeEl)) {\r\n          const localTime = timeEl.textContent.trim();\r\n          const moscowTime = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_0__.calibrateToMoscowTime)(localTime);\r\n          const today = new Intl.DateTimeFormat('en-CA').format(new Date());\r\n          const url = `https://klavogonki.ru/chatlogs/${today}.html#${moscowTime}`;\r\n          window.open(url, '_blank');\r\n        }\r\n      });\r\n      this.panel._delegatedClickAttached = true;\r\n    }\r\n  }\r\n\r\n  getChatHistory() {\r\n    return Array.from(this.messageMap.values());\r\n  }\r\n\r\n  refreshMessages(connectionStatus = false) {\r\n    const messageText = connectionStatus\r\n      ? \"Chat connection established. ✓\"\r\n      : \"Chat connection lost. Reconnecting...\";\r\n\r\n    // Use a consistent ID for the connection status message.\r\n    const systemMessageId = 'chat-connection';\r\n\r\n    // Remove the specific system connection message from our map and from our in‑memory rendered IDs.\r\n    this.messageMap.delete(systemMessageId);\r\n    this.renderedMessageIds.delete(systemMessageId);\r\n\r\n    // Create and add the new system message.\r\n    const systemMessage = {\r\n      id: systemMessageId,\r\n      from: \"System\",\r\n      text: messageText,\r\n      isPrivate: false,\r\n      recipient: null,\r\n      isSystem: true,\r\n      pending: false,\r\n      timestamp: new Date().toLocaleTimeString('en-GB', { hour12: false })\r\n    };\r\n\r\n    this.messageMap.set(systemMessageId, systemMessage);\r\n    this.updateConnectionStatusInUI(systemMessage);\r\n  }\r\n\r\n  updateConnectionStatusInUI(systemMessage) {\r\n    // Remove all pending icons.\r\n    this.panel.querySelectorAll('.pending-emoji').forEach(el => el.remove());\r\n\r\n    // Remove the existing system message element (if any).\r\n    const systemMessageElement = this.panel.querySelector(`[data-message-id=\"${systemMessage.id}\"]`);\r\n    if (systemMessageElement) {\r\n      systemMessageElement.remove();\r\n    }\r\n\r\n    // Re-render messages (using the updated DOM as the source of rendered IDs).\r\n    this.updatePanel();\r\n  }\r\n}\r\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/managers/messageManager.js?");

/***/ }),

/***/ "./src/managers/userManager.js":
/*!*************************************!*\
  !*** ./src/managers/userManager.js ***!
  \*************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (/* binding */ UserManager)\n/* harmony export */ });\n/* harmony import */ var _data_definitions_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../data/definitions.js */ \"./src/data/definitions.js\");\n/* harmony import */ var _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../helpers/helpers.js */ \"./src/helpers/helpers.js\");\n/* harmony import */ var _data_animations_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../data/animations.js */ \"./src/data/animations.js\");\n/* harmony import */ var _helpers_chatUsernameColors_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../helpers/chatUsernameColors.js */ \"./src/helpers/chatUsernameColors.js\");\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nclass UserManager {\r\n  constructor(containerId = 'user-list') {\r\n    this.container = document.getElementById(containerId);\r\n    this.activeUsers = new Map();\r\n    this.isFirstLoad = true;\r\n    this.avatarCache = this.loadAvatarCache();\r\n    this.cacheDate = new Date().toDateString();\r\n    this.changes = false;\r\n    this.newUserJIDs = [];\r\n    this.updatedUserJIDs = [];\r\n\r\n    // Role-based icons\r\n    this.roleIcons = {\r\n      'visitor': '🚫',\r\n      'participant': '',\r\n      'moderator': '⚔️️'\r\n    };\r\n\r\n    // Role priority for sorting\r\n    this.rolePriority = {\r\n      'moderator': 1,\r\n      'participant': 2,\r\n      'visitor': 3\r\n    };\r\n\r\n    // Attach event listeners\r\n    this.setupEventListeners();\r\n  }\r\n\r\n  loadAvatarCache() {\r\n    try {\r\n      const cacheData = localStorage.getItem('userAvatarCache');\r\n      if (cacheData) {\r\n        const cache = JSON.parse(cacheData);\r\n        if (cache.date === new Date().toDateString()) {\r\n          console.log(\"🗃️ Loaded avatar cache from localStorage\");\r\n          return cache.avatars || {};\r\n        } else {\r\n          console.log(\"🗃️ Avatar cache expired (new day), creating fresh cache\");\r\n          return {};\r\n        }\r\n      }\r\n    } catch (error) {\r\n      console.error(\"Error loading avatar cache:\", error);\r\n    }\r\n    return {};\r\n  }\r\n\r\n  saveAvatarCache() {\r\n    try {\r\n      localStorage.setItem('userAvatarCache', JSON.stringify({\r\n        date: this.cacheDate,\r\n        avatars: this.avatarCache\r\n      }));\r\n    } catch (error) {\r\n      console.error(\"Error saving avatar cache:\", error);\r\n    }\r\n  }\r\n\r\n  setupEventListeners() {\r\n    this.container.addEventListener('click', (event) => {\r\n      // Handle username clicks\r\n      if (event.target.classList.contains('username-clickable')) {\r\n        const dataUserId = event.target.getAttribute('data-user-id');\r\n        if (dataUserId) {\r\n          if (event.ctrlKey) {\r\n            // Ctrl+Click: Start private chat with user\r\n            const username = event.target.textContent.trim();\r\n            const messageInput = document.getElementById('message-input');\r\n            messageInput.value = `/pm ${username} `;\r\n            (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.handlePrivateMessageInput)(messageInput);\r\n            messageInput.focus();\r\n          } else {\r\n            // Normal click: Navigate to profile\r\n            const userIdValue = dataUserId.split('/')[1].split('#')[0]; \r\n            const navigateToProfileUrl = `https://klavogonki.ru/u/#/${userIdValue}/`;\r\n            window.location.href = navigateToProfileUrl;\r\n          }\r\n        }\r\n      }\r\n\r\n      // Handle game indicator clicks\r\n      if (event.target.closest('.game-indicator')) {\r\n        const gameIndicator = event.target.closest('.game-indicator');\r\n        const gameId = gameIndicator.getAttribute('data-game-id');\r\n        if (gameId) {\r\n          event.stopPropagation();\r\n          window.location.href = `https://klavogonki.ru/g/?gmid=${gameId}`;\r\n        }\r\n      }\r\n    });\r\n  }\r\n\r\n  async updatePresence(xmlResponse) {\r\n    const parser = new DOMParser();\r\n    const doc = parser.parseFromString(xmlResponse, \"text/xml\");\r\n    const presences = doc.getElementsByTagName(\"presence\");\r\n\r\n    if (xmlResponse.includes('<presence id=\"initialChatLoad\"')) {\r\n      console.log(\"🔄 Initial room join detected, requesting full roster\");\r\n      this.requestFullRoster();\r\n      return;\r\n    }\r\n\r\n    // Reset change tracking variables for this update cycle\r\n    this.changes = false;\r\n    this.newUserJIDs = [];\r\n    this.updatedUserJIDs = [];\r\n\r\n    for (let i = 0; i < presences.length; i++) {\r\n      const presence = presences[i];\r\n      const from = presence.getAttribute('from');\r\n      const type = presence.getAttribute('type');\r\n\r\n      // Skip if not from the conference room\r\n      if (!from || !from.includes('[email protected]/')) {\r\n        continue;\r\n      }\r\n\r\n      // Extract username from JID\r\n      const usernameFromJid = from.split('/').pop();\r\n      if (!usernameFromJid) continue;\r\n\r\n      // Skip Клавобот (unified check)\r\n      if (usernameFromJid.toLowerCase() === 'клавобот' || from.toLowerCase().includes('#клавобот')) {\r\n        continue;\r\n      }\r\n\r\n      // Handle user leaving\r\n      if (type === 'unavailable') {\r\n        if (this.activeUsers.has(from)) {\r\n          const departingUser = this.activeUsers.get(from);\r\n          const userId = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.extractUserId)(from);\r\n          const cleanLogin = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.extractUsername)(departingUser.login);\r\n\r\n          if (!this.isFirstLoad) {\r\n            // console.log(`🚪 User left: ${cleanLogin} ID: (${userId})`);\r\n          }\r\n          this.activeUsers.delete(from);\r\n          this.changes = true;\r\n        }\r\n        continue;\r\n      }\r\n\r\n      const existingUser = this.activeUsers.get(from) || {};\r\n      const userId = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.extractUserId)(from);\r\n      const cachedAvatarInfo = this.avatarCache[userId];\r\n\r\n      // Initialize userData\r\n      let userData = {\r\n        jid: from,\r\n        login: usernameFromJid,\r\n        color: '#777',\r\n        // Normalize the username before calling getColor:\r\n        usernameColor: _helpers_chatUsernameColors_js__WEBPACK_IMPORTED_MODULE_3__.usernameColors.getColor((0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.extractUsername)(usernameFromJid)),\r\n        role: 'participant',\r\n        gameId: null,\r\n        avatar: null\r\n      };\r\n\r\n      // Process x elements\r\n      const xElements = presence.getElementsByTagName(\"x\");\r\n      let foundAvatar = false;\r\n\r\n      Array.from(xElements).forEach(element => {\r\n        const xmlns = element.getAttribute(\"xmlns\");\r\n\r\n        if (xmlns === \"klavogonki:userdata\") {\r\n          const userNode = element.getElementsByTagName(\"user\")[0];\r\n\r\n          if (userNode) {\r\n            const loginElement = userNode.getElementsByTagName(\"login\")[0];\r\n            if (loginElement && loginElement.textContent) {\r\n              userData.login = loginElement.textContent;\r\n              userData.usernameColor = _helpers_chatUsernameColors_js__WEBPACK_IMPORTED_MODULE_3__.usernameColors.getColor((0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.extractUsername)(userData.login));\r\n            }\r\n\r\n            const avatarElement = userNode.getElementsByTagName(\"avatar\")[0];\r\n            if (avatarElement && avatarElement.textContent) {\r\n              userData.avatar = avatarElement.textContent;\r\n              foundAvatar = true;\r\n            }\r\n\r\n            const moderatorNode = userNode.getElementsByTagName(\"moderator\")[0];\r\n            if (moderatorNode && moderatorNode.textContent === '1') {\r\n              userData.role = 'moderator';\r\n            }\r\n          }\r\n\r\n          const gameIdElement = element.getElementsByTagName(\"game_id\")[0];\r\n          if (gameIdElement && gameIdElement.textContent) {\r\n            userData.gameId = gameIdElement.textContent;\r\n          }\r\n        }\r\n\r\n        if (xmlns === \"http://jabber.org/protocol/muc#user\") {\r\n          const itemNode = element.getElementsByTagName(\"item\")[0];\r\n          if (itemNode) {\r\n            const role = itemNode.getAttribute(\"role\");\r\n            if (role && userData.role !== 'moderator') {\r\n              userData.role = role;\r\n            }\r\n          }\r\n        }\r\n      });\r\n\r\n      // If no avatar in update but exists in user data, keep it\r\n      if (!foundAvatar && existingUser && existingUser.avatar) {\r\n        userData.avatar = existingUser.avatar;\r\n      }\r\n\r\n      // Handle avatar (use cache or set default)\r\n      if (!userData.avatar && cachedAvatarInfo) {\r\n        if (cachedAvatarInfo.hasAvatar) {\r\n          userData.avatar = cachedAvatarInfo.avatarUrl;\r\n        }\r\n      }\r\n\r\n      const cleanLogin = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.extractUsername)(userData.login);\r\n\r\n      // Determine if user is new or updated\r\n      if (!this.activeUsers.has(from)) {\r\n        if (!this.isFirstLoad) {\r\n          // console.log(`👤 User joined: ${cleanLogin} ID: (${userId})`);\r\n        }\r\n        this.activeUsers.set(from, userData);\r\n        this.changes = true;\r\n        this.newUserJIDs.push(from);\r\n      } else if (JSON.stringify(existingUser) !== JSON.stringify(userData)) {\r\n        this.activeUsers.set(from, userData);\r\n        this.changes = true;\r\n        this.updatedUserJIDs.push(from);\r\n      }\r\n    }\r\n\r\n    if (this.changes) {\r\n      this.updateUI();\r\n    }\r\n  }\r\n\r\n  updateUI() {\r\n    // Build map of existing DOM elements\r\n    const existingElements = new Map();\r\n    this.container.querySelectorAll('.user-item').forEach(el => {\r\n      existingElements.set(el.getAttribute('data-jid'), el);\r\n    });\r\n\r\n    // Sort users by role and username\r\n    const sortedUsers = Array.from(this.activeUsers.values()).sort((a, b) => {\r\n      const priorityDiff = this.rolePriority[a.role] - this.rolePriority[b.role];\r\n      return priorityDiff !== 0 ? priorityDiff :\r\n        (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.extractUsername)(a.login).localeCompare((0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.extractUsername)(b.login));\r\n    });\r\n\r\n    // Build the updated list\r\n    const fragment = document.createDocumentFragment();\r\n    sortedUsers.forEach(user => {\r\n      let userElement = existingElements.get(user.jid);\r\n      const userId = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.extractUserId)(user.jid);\r\n      const cleanLogin = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.extractUsername)(user.login);\r\n\r\n      // If element doesn't exist, create it\r\n      if (!userElement) {\r\n        userElement = document.createElement('div');\r\n        userElement.classList.add('user-item');\r\n        userElement.setAttribute('data-jid', user.jid);\r\n        const roleIcon = this.roleIcons[user.role];\r\n\r\n        const avatarContainer = document.createElement('span');\r\n        avatarContainer.className = 'avatar-container';\r\n        this.setUserAvatar(avatarContainer, user, userId, cleanLogin);\r\n\r\n        // Create user info container\r\n        const userInfo = document.createElement('div');\r\n        userInfo.className = 'user-info';\r\n        userInfo.innerHTML = `\r\n          <div class=\"username\" style=\"color: ${user.usernameColor}\">\r\n            <span class=\"username-clickable\" data-user-id=\"${user.jid}\">${cleanLogin}</span>\r\n            <span class=\"role ${user.role}\">${roleIcon}</span>\r\n          </div>\r\n        `;\r\n\r\n        // Append avatar and user info\r\n        userElement.appendChild(avatarContainer);\r\n        userElement.appendChild(userInfo);\r\n      } else {\r\n        // Update existing element if needed\r\n        if (!userElement.querySelector('.avatar-container')) {\r\n          const avatarContainer = document.createElement('span');\r\n          avatarContainer.className = 'avatar-container';\r\n          this.setUserAvatar(avatarContainer, user, userId, cleanLogin);\r\n          userElement.insertBefore(avatarContainer, userElement.firstChild);\r\n        }\r\n\r\n        // Remove from map so remaining elements are those to be removed\r\n        existingElements.delete(user.jid);\r\n\r\n        // Update role icon if changed\r\n        const roleElement = userElement.querySelector('.role');\r\n        const newRoleIcon = this.roleIcons[user.role];\r\n        if (roleElement && roleElement.textContent !== newRoleIcon) {\r\n          roleElement.textContent = newRoleIcon;\r\n          if (!roleElement.classList.contains(user.role)) {\r\n            roleElement.className = `role ${user.role}`;\r\n          }\r\n        }\r\n\r\n        // Update username color if needed\r\n        const usernameElement = userElement.querySelector('.username');\r\n        if (usernameElement && usernameElement.style.color !== user.usernameColor) {\r\n          usernameElement.style.color = user.usernameColor;\r\n        }\r\n      }\r\n\r\n      // Handle game indicator\r\n      this.updateGameIndicator(userElement, user);\r\n\r\n      // Append to fragment\r\n      fragment.appendChild(userElement);\r\n    });\r\n\r\n    // Clear container and append fragment\r\n    this.container.innerHTML = '';\r\n    this.container.appendChild(fragment);\r\n\r\n    // Remove elements for users no longer active\r\n    existingElements.forEach((el) => {\r\n      if (el && el.parentNode) {\r\n        el.remove();\r\n      }\r\n    });\r\n\r\n    // Apply shake effect for new users\r\n    if (!this.isFirstLoad) {\r\n      this.newUserJIDs.forEach(jid => {\r\n        const userElement = this.container.querySelector(`.user-item[data-jid=\"${jid}\"]`);\r\n        if (userElement && userElement.parentNode) {\r\n          (0,_data_animations_js__WEBPACK_IMPORTED_MODULE_2__.addShakeEffect)(userElement);\r\n        }\r\n      });\r\n    }\r\n\r\n    if (this.isFirstLoad) {\r\n      this.isFirstLoad = false;\r\n    }\r\n  }\r\n\r\n  setUserAvatar(avatarContainer, user, userId, cleanLogin) {\r\n    const cachedAvatarInfo = this.avatarCache[userId];\r\n\r\n    // Display avatar based on available information\r\n    if (user.avatar) {\r\n      const avatarUrl = `${_data_definitions_js__WEBPACK_IMPORTED_MODULE_0__.BASE_URL}/storage/avatars/${userId}_big.png`;\r\n      const avatarImg = document.createElement('img');\r\n      avatarImg.className = 'user-avatar image-avatar';\r\n      avatarImg.src = avatarUrl;\r\n      avatarImg.alt = `${cleanLogin}'s avatar`;\r\n\r\n      // Handle error by replacing with emoji\r\n      avatarImg.addEventListener('error', () => {\r\n        const fallbackEmoji = cachedAvatarInfo?.emoji || (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.getRandomEmojiAvatar)();\r\n        avatarContainer.innerHTML = '';\r\n        const fallbackSpan = document.createElement('span');\r\n        fallbackSpan.className = 'user-avatar svg-avatar';\r\n        fallbackSpan.textContent = fallbackEmoji;\r\n        avatarContainer.appendChild(fallbackSpan);\r\n\r\n        // Update cache\r\n        this.avatarCache[userId] = {\r\n          hasAvatar: false,\r\n          emoji: fallbackEmoji\r\n        };\r\n        this.saveAvatarCache();\r\n      });\r\n\r\n      // On successful load, update cache\r\n      avatarImg.addEventListener('load', () => {\r\n        // Only log first time we detect an avatar\r\n        if (!cachedAvatarInfo || !cachedAvatarInfo.hasAvatar) {\r\n          // console.log(`🖼️ Using image avatar for User: ${cleanLogin} ID: (${userId})`);\r\n        }\r\n        this.avatarCache[userId] = {\r\n          hasAvatar: true,\r\n          avatarUrl: avatarUrl\r\n        };\r\n        this.saveAvatarCache();\r\n      });\r\n\r\n      avatarContainer.appendChild(avatarImg);\r\n\r\n    } else if (cachedAvatarInfo) {\r\n      // Use cached information\r\n      if (cachedAvatarInfo.hasAvatar) {\r\n        const avatarImg = document.createElement('img');\r\n        avatarImg.className = 'user-avatar image-avatar';\r\n        avatarImg.src = cachedAvatarInfo.avatarUrl;\r\n        avatarImg.alt = `${cleanLogin}'s avatar`;\r\n\r\n        // Handle error if cache is incorrect\r\n        avatarImg.addEventListener('error', () => {\r\n          const fallbackEmoji = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.getRandomEmojiAvatar)();\r\n          avatarContainer.innerHTML = '';\r\n          const fallbackSpan = document.createElement('span');\r\n          fallbackSpan.className = 'user-avatar svg-avatar';\r\n          fallbackSpan.textContent = fallbackEmoji;\r\n          avatarContainer.appendChild(fallbackSpan);\r\n\r\n          // Update cache\r\n          this.avatarCache[userId] = {\r\n            hasAvatar: false,\r\n            emoji: fallbackEmoji\r\n          };\r\n          this.saveAvatarCache();\r\n        });\r\n\r\n        avatarContainer.appendChild(avatarImg);\r\n      } else {\r\n        // Use cached emoji\r\n        const fallbackSpan = document.createElement('span');\r\n        fallbackSpan.className = 'user-avatar svg-avatar';\r\n        fallbackSpan.textContent = cachedAvatarInfo.emoji;\r\n        avatarContainer.appendChild(fallbackSpan);\r\n\r\n        // Only log first time we use an emoji avatar\r\n        if (!this.avatarCache[userId] || !this.avatarCache[userId].hasEmoji) {\r\n          // console.log(`😊 Using emoji avatar for User: ${cleanLogin} ID: (${userId}): ${cachedAvatarInfo.emoji}`);\r\n          // Mark that we've logged this emoji usage\r\n          this.avatarCache[userId].hasEmoji = true;\r\n          this.saveAvatarCache();\r\n        }\r\n      }\r\n    } else {\r\n      // No cached info - try to fetch avatar\r\n      const avatarUrl = `${_data_definitions_js__WEBPACK_IMPORTED_MODULE_0__.BASE_URL}/storage/avatars/${userId}_big.png`;\r\n      const avatarImg = document.createElement('img');\r\n      avatarImg.className = 'user-avatar image-avatar';\r\n      avatarImg.src = avatarUrl;\r\n      avatarImg.alt = `${cleanLogin}'s avatar`;\r\n\r\n      // Handle error\r\n      avatarImg.addEventListener('error', () => {\r\n        const fallbackEmoji = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.getRandomEmojiAvatar)();\r\n        avatarContainer.innerHTML = '';\r\n        const fallbackSpan = document.createElement('span');\r\n        fallbackSpan.className = 'user-avatar svg-avatar';\r\n        fallbackSpan.textContent = fallbackEmoji;\r\n        avatarContainer.appendChild(fallbackSpan);\r\n\r\n        // Log first time using emoji\r\n        console.log(`😊 Using emoji avatar for User: ${cleanLogin} ID: (${userId}): ${fallbackEmoji}`);\r\n\r\n        // Cache negative result\r\n        this.avatarCache[userId] = {\r\n          hasAvatar: false,\r\n          emoji: fallbackEmoji,\r\n          hasEmoji: true\r\n        };\r\n        this.saveAvatarCache();\r\n      });\r\n\r\n      // On successful load, cache positive result\r\n      avatarImg.addEventListener('load', () => {\r\n        console.log(`🖼️ Using image avatar for User: ${cleanLogin} ID: (${userId})`);\r\n        this.avatarCache[userId] = {\r\n          hasAvatar: true,\r\n          avatarUrl: avatarUrl\r\n        };\r\n        this.saveAvatarCache();\r\n      });\r\n\r\n      avatarContainer.appendChild(avatarImg);\r\n    }\r\n  }\r\n\r\n  updateGameIndicator(userElement, user) {\r\n    let gameIndicatorElement = userElement.querySelector('.game-indicator');\r\n\r\n    if (user.gameId) {\r\n      if (!gameIndicatorElement || gameIndicatorElement.getAttribute('data-game-id') !== user.gameId) {\r\n        const newIndicatorHTML = `<span class=\"game-indicator\" title=\"${user.gameId}\" data-game-id=\"${user.gameId}\">\r\n                                    <span class=\"traffic-icon\">🚦</span>\r\n                                  </span>`;\r\n        if (gameIndicatorElement) {\r\n          gameIndicatorElement.outerHTML = newIndicatorHTML;\r\n        } else {\r\n          const usernameContainer = userElement.querySelector('.username');\r\n          usernameContainer.insertAdjacentHTML('beforeend', newIndicatorHTML);\r\n        }\r\n      }\r\n    } else if (gameIndicatorElement && gameIndicatorElement.parentNode) {\r\n      gameIndicatorElement.remove();\r\n    }\r\n  }\r\n\r\n  async requestFullRoster() {\r\n    console.log(\"📑 Would request full roster here (using existing data for now)\");\r\n    this.updateUI();\r\n  }\r\n}\n\n//# sourceURL=webpack://tampermonkey-script/./src/managers/userManager.js?");

/***/ }),

/***/ "./src/styles/chatUsernameColors.scss":
/*!********************************************!*\
  !*** ./src/styles/chatUsernameColors.scss ***!
  \********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleDomAPI.js */ \"./node_modules/style-loader/dist/runtime/styleDomAPI.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertBySelector.js */ \"./node_modules/style-loader/dist/runtime/insertBySelector.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js */ \"./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertStyleElement.js */ \"./node_modules/style-loader/dist/runtime/insertStyleElement.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleTagTransform.js */ \"./node_modules/style-loader/dist/runtime/styleTagTransform.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_chatUsernameColors_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! !!../../node_modules/css-loader/dist/cjs.js!../../node_modules/sass-loader/dist/cjs.js!./chatUsernameColors.scss */ \"./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/chatUsernameColors.scss\");\n\n      \n      \n      \n      \n      \n      \n      \n      \n      \n\nvar options = {};\n\noptions.styleTagTransform = (_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default());\noptions.setAttributes = (_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default());\n\n      options.insert = _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default().bind(null, \"head\");\n    \noptions.domAPI = (_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default());\noptions.insertStyleElement = (_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default());\n\nvar update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_chatUsernameColors_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"], options);\n\n\n\n\n       /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_chatUsernameColors_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"] && _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_chatUsernameColors_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals ? _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_chatUsernameColors_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals : undefined);\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/styles/chatUsernameColors.scss?");

/***/ }),

/***/ "./src/styles/emojiPanel.scss":
/*!************************************!*\
  !*** ./src/styles/emojiPanel.scss ***!
  \************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleDomAPI.js */ \"./node_modules/style-loader/dist/runtime/styleDomAPI.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertBySelector.js */ \"./node_modules/style-loader/dist/runtime/insertBySelector.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js */ \"./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertStyleElement.js */ \"./node_modules/style-loader/dist/runtime/insertStyleElement.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleTagTransform.js */ \"./node_modules/style-loader/dist/runtime/styleTagTransform.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_emojiPanel_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! !!../../node_modules/css-loader/dist/cjs.js!../../node_modules/sass-loader/dist/cjs.js!./emojiPanel.scss */ \"./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/emojiPanel.scss\");\n\n      \n      \n      \n      \n      \n      \n      \n      \n      \n\nvar options = {};\n\noptions.styleTagTransform = (_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default());\noptions.setAttributes = (_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default());\n\n      options.insert = _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default().bind(null, \"head\");\n    \noptions.domAPI = (_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default());\noptions.insertStyleElement = (_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default());\n\nvar update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_emojiPanel_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"], options);\n\n\n\n\n       /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_emojiPanel_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"] && _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_emojiPanel_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals ? _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_emojiPanel_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals : undefined);\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/styles/emojiPanel.scss?");

/***/ }),

/***/ "./src/styles/helpPanel.scss":
/*!***********************************!*\
  !*** ./src/styles/helpPanel.scss ***!
  \***********************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleDomAPI.js */ \"./node_modules/style-loader/dist/runtime/styleDomAPI.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertBySelector.js */ \"./node_modules/style-loader/dist/runtime/insertBySelector.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js */ \"./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertStyleElement.js */ \"./node_modules/style-loader/dist/runtime/insertStyleElement.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleTagTransform.js */ \"./node_modules/style-loader/dist/runtime/styleTagTransform.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_helpPanel_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! !!../../node_modules/css-loader/dist/cjs.js!../../node_modules/sass-loader/dist/cjs.js!./helpPanel.scss */ \"./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/helpPanel.scss\");\n\n      \n      \n      \n      \n      \n      \n      \n      \n      \n\nvar options = {};\n\noptions.styleTagTransform = (_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default());\noptions.setAttributes = (_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default());\n\n      options.insert = _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default().bind(null, \"head\");\n    \noptions.domAPI = (_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default());\noptions.insertStyleElement = (_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default());\n\nvar update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_helpPanel_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"], options);\n\n\n\n\n       /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_helpPanel_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"] && _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_helpPanel_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals ? _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_helpPanel_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals : undefined);\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/styles/helpPanel.scss?");

/***/ }),

/***/ "./src/styles/style.scss":
/*!*******************************!*\
  !*** ./src/styles/style.scss ***!
  \*******************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleDomAPI.js */ \"./node_modules/style-loader/dist/runtime/styleDomAPI.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertBySelector.js */ \"./node_modules/style-loader/dist/runtime/insertBySelector.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js */ \"./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertStyleElement.js */ \"./node_modules/style-loader/dist/runtime/insertStyleElement.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleTagTransform.js */ \"./node_modules/style-loader/dist/runtime/styleTagTransform.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_style_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! !!../../node_modules/css-loader/dist/cjs.js!../../node_modules/sass-loader/dist/cjs.js!./style.scss */ \"./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/style.scss\");\n\n      \n      \n      \n      \n      \n      \n      \n      \n      \n\nvar options = {};\n\noptions.styleTagTransform = (_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default());\noptions.setAttributes = (_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default());\n\n      options.insert = _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default().bind(null, \"head\");\n    \noptions.domAPI = (_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default());\noptions.insertStyleElement = (_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default());\n\nvar update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_style_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"], options);\n\n\n\n\n       /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_style_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"] && _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_style_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals ? _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_style_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals : undefined);\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/styles/style.scss?");

/***/ }),

/***/ "./src/styles/updateCheck.scss":
/*!*************************************!*\
  !*** ./src/styles/updateCheck.scss ***!
  \*************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleDomAPI.js */ \"./node_modules/style-loader/dist/runtime/styleDomAPI.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertBySelector.js */ \"./node_modules/style-loader/dist/runtime/insertBySelector.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js */ \"./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/insertStyleElement.js */ \"./node_modules/style-loader/dist/runtime/insertStyleElement.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! !../../node_modules/style-loader/dist/runtime/styleTagTransform.js */ \"./node_modules/style-loader/dist/runtime/styleTagTransform.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_updateCheck_scss__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! !!../../node_modules/css-loader/dist/cjs.js!../../node_modules/sass-loader/dist/cjs.js!./updateCheck.scss */ \"./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/styles/updateCheck.scss\");\n\n      \n      \n      \n      \n      \n      \n      \n      \n      \n\nvar options = {};\n\noptions.styleTagTransform = (_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default());\noptions.setAttributes = (_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default());\n\n      options.insert = _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default().bind(null, \"head\");\n    \noptions.domAPI = (_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default());\noptions.insertStyleElement = (_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default());\n\nvar update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_updateCheck_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"], options);\n\n\n\n\n       /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_updateCheck_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"] && _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_updateCheck_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals ? _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_updateCheck_scss__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals : undefined);\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/styles/updateCheck.scss?");

/***/ }),

/***/ "./src/xmpp/xmppClient.js":
/*!********************************!*\
  !*** ./src/xmpp/xmppClient.js ***!
  \********************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   createXMPPClient: () => (/* binding */ createXMPPClient)\n/* harmony export */ });\n/* harmony import */ var _data_definitions_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../data/definitions.js */ \"./src/data/definitions.js\");\n/* harmony import */ var _helpers_chatUsernameColors_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../helpers/chatUsernameColors.js */ \"./src/helpers/chatUsernameColors.js\");\n/* harmony import */ var _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../helpers/helpers.js */ \"./src/helpers/helpers.js\");\n\r\n\r\n\r\n\r\n\r\nfunction createXMPPClient(xmppConnection, userManager, messageManager, username) {\r\n  // Compact wrapper functions.\r\n  const safeUpdatePresence = (xmlResponse) =>\r\n    xmlResponse && userManager.updatePresence(xmlResponse);\r\n\r\n  const safeProcessMessages = (xmlResponse) =>\r\n    xmlResponse && messageManager.processMessages(xmlResponse);\r\n\r\n  // Initialize userInfo as null\r\n  let userInfo = null;\r\n\r\n  // Function to calculate or retrieve user info\r\n  function getUserInfo() {\r\n    // If userInfo already exists, return it immediately\r\n    if (userInfo) {\r\n      return userInfo;\r\n    }\r\n\r\n    // Only proceed if the sessionStorage key exists\r\n    if (!sessionStorage.getItem('usernameColors')) {\r\n      console.log('usernameColors key does not exist in sessionStorage, skipping userInfo calculation');\r\n      return null; // Or any appropriate value to indicate we didn't calculate\r\n    }\r\n\r\n    // If we reach here, sessionStorage key exists\r\n    try {\r\n      const usernameColors = JSON.parse(sessionStorage.getItem('usernameColors'));\r\n\r\n      const cleanedUsername = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__.extractUsername)(username);\r\n      const usernameKey = cleanedUsername.toLowerCase();\r\n      const storedColor = usernameColors[usernameKey] || '#ff00c6';\r\n      const optimizedColor = (0,_helpers_chatUsernameColors_js__WEBPACK_IMPORTED_MODULE_1__.optimizeColor)(storedColor);\r\n      const baseAvatarPath = `/storage/avatars/${username.split('#')[0]}.png`;\r\n      const timestamp = Math.floor(Date.now() / 1000);\r\n\r\n      // Store the calculated info\r\n      userInfo = {\r\n        cleanedUsername,\r\n        usernameKey,\r\n        storedColor,\r\n        optimizedColor,\r\n        baseAvatarPath,\r\n        timestamp\r\n      };\r\n\r\n      return userInfo;\r\n    } catch (error) {\r\n      console.error('Error parsing usernameColors from sessionStorage:', error);\r\n      return null; // Or any appropriate value to indicate failure\r\n    }\r\n  }\r\n\r\n  const xmppClient = {\r\n    userManager,\r\n    messageManager,\r\n    presenceInterval: null,\r\n    isReconnecting: false,\r\n    isConnected: false,\r\n    // Queue of messages waiting to be sent.\r\n    messageQueue: [],\r\n    // Store last sent message to prevent duplicates\r\n    lastSentMessage: null,\r\n\r\n    // Helper: Create the XML stanza for a message.\r\n    _createMessageStanza(text, messageId, isPrivate, fullJid) {\r\n      // Get user info, calculating it if necessary\r\n      const info = getUserInfo();\r\n\r\n      // Retrieve chatUsernameColor from localStorage or fallback to precalculated optimizedColor if available\r\n      const storedColor = localStorage.getItem('chatUsernameColor');\r\n      const chatUsernameColor = storedColor ? storedColor : (info ? info.optimizedColor : \"#777\");\r\n\r\n      // Create the user data block using pre-calculated properties and the determined background color\r\n      const userDataBlock = `\r\n      <x xmlns='klavogonki:userdata'>\r\n        <user>\r\n          <login>${info.cleanedUsername}</login>\r\n          <avatar>${info.baseAvatarPath}?updated=${info.timestamp}</avatar>\r\n          <background>${chatUsernameColor}</background>\r\n        </user>\r\n      </x>\r\n      `;\r\n\r\n      // Determine the destination and message type\r\n      const destination = isPrivate && fullJid\r\n        ? fullJid\r\n        : '[email protected]';\r\n      const messageType = isPrivate ? 'chat' : 'groupchat';\r\n\r\n      // Create the full message stanza with readable formatting\r\n      const formattedXML = `\r\n      <body rid='${xmppConnection.nextRid()}' sid='${xmppConnection.sid}' xmlns='http://jabber.org/protocol/httpbind'>\r\n        <message \r\n          from='${username}@jabber.klavogonki.ru/web' \r\n          to='${destination}' \r\n          type='${messageType}' \r\n          id='${messageId}' \r\n          xmlns='jabber:client'>\r\n          <body>${text}</body>\r\n          ${userDataBlock}\r\n        </message>\r\n      </body>\r\n      `;\r\n\r\n      // Return the compacted version for actual use\r\n      return (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__.compactXML)(formattedXML);\r\n    },\r\n\r\n    // Process queued messages sequentially.\r\n    async processQueue() {\r\n      if (!this.isConnected || this.isReconnecting) return;\r\n\r\n      while (this.messageQueue.length > 0) {\r\n        const msg = this.messageQueue[0]; // Peek the first message.\r\n        const messageStanza = this._createMessageStanza(msg.text, msg.id, msg.isPrivate, msg.fullJid);\r\n        try {\r\n          await xmppConnection.sendRequestWithRetry(messageStanza);\r\n          // On success, remove the message from the queue.\r\n          this.messageQueue.shift();\r\n          // Optionally, update the UI to remove the pending flag.\r\n          messageManager.updatePendingStatus(msg.id, false);\r\n          safeProcessMessages(null);\r\n        } catch (error) {\r\n          console.error(`Failed to send queued message (${msg.id}): ${error.message}`);\r\n          // Stop processing; the message remains in the queue for later retry.\r\n          break;\r\n        }\r\n      }\r\n    },\r\n\r\n    async connect() {\r\n      try {\r\n        if (this.presenceInterval) {\r\n          clearInterval(this.presenceInterval);\r\n          this.presenceInterval = null;\r\n        }\r\n        let retries = 5;\r\n        while (retries > 0 && !this.isConnected) {\r\n          try {\r\n            const session = await xmppConnection.connect();\r\n            console.log('💬 Step 8: Joining chat room...');\r\n\r\n            // Get user info, calculating it if necessary\r\n            const info = getUserInfo();\r\n\r\n            // Create user data block, using either info or fallbacks\r\n            const cleanedUsername = info ? info.cleanedUsername : (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__.extractUsername)(username);\r\n            const baseAvatarPath = info ? info.baseAvatarPath : `/storage/avatars/${username.split('#')[0]}.png`;\r\n            const timestamp = info ? info.timestamp : Math.floor(Date.now() / 1000);\r\n            const backgroundColor = info ? info.optimizedColor : \"#000000\";\r\n\r\n            const joinPayload = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__.compactXML)(`\r\n            <body rid='${xmppConnection.nextRid()}' xmlns='http://jabber.org/protocol/httpbind' sid='${session.sid}'>\r\n              <presence from='${username}@jabber.klavogonki.ru/web' to='[email protected]/${username}' xmlns='jabber:client'>\r\n                <x xmlns='http://jabber.org/protocol/muc'/>\r\n                <x xmlns='klavogonki:userdata'>\r\n                  <user>\r\n                    <login>${cleanedUsername}</login>\r\n                    <avatar>${baseAvatarPath}?updated=${timestamp}</avatar>\r\n                    <background>${backgroundColor}</background>\r\n                  </user>\r\n                </x>\r\n              </presence>\r\n            </body>\r\n            `);\r\n\r\n            const joinResponse = await xmppConnection.sendRequestWithRetry(joinPayload);\r\n\r\n            console.log('📥 Join response:', joinResponse);\r\n\r\n            safeUpdatePresence(joinResponse);\r\n            safeProcessMessages(joinResponse);\r\n\r\n            const infoPayload = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__.compactXML)(`\r\n              <body \r\n                rid='${xmppConnection.nextRid()}' \r\n                sid='${session.sid}' \r\n                xmlns='http://jabber.org/protocol/httpbind'>\r\n                <iq \r\n                  type='get' \r\n                  id='info_${Math.random().toString(36).substring(2, 10)}' \r\n                  xmlns='jabber:client' \r\n                  to='[email protected]'>\r\n                  <query xmlns='http://jabber.org/protocol/disco#info'/>\r\n                </iq>\r\n              </body>\r\n            `);\r\n\r\n            await xmppConnection.sendRequestWithRetry(infoPayload);\r\n            console.log('🚀 Step 10: Connected! Starting presence updates...');\r\n\r\n            this.isConnected = true;\r\n            if (this.isReconnecting) {\r\n              (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__.showChatAlert)(\"Chat connected successfully!\", { type: 'success' });\r\n              messageManager.refreshMessages(true);\r\n              this.isReconnecting = false;\r\n            }\r\n            this.startPresencePolling(xmppConnection);\r\n            // Process any queued messages.\r\n            this.processQueue();\r\n            break;\r\n          } catch (error) {\r\n            console.error(`💥 Connection error: ${error.message}`);\r\n            retries--;\r\n            if (retries === 0) {\r\n              console.log('⏳ Scheduling reconnection attempt in 5 seconds...');\r\n              this.isReconnecting = true;\r\n              setTimeout(() => this.connect(), _data_definitions_js__WEBPACK_IMPORTED_MODULE_0__.reconnectionDelay);\r\n            } else {\r\n              console.log(`🔄 Retrying connection... (${retries} attempts left)`);\r\n              await new Promise(resolve => setTimeout(resolve, 5000));\r\n            }\r\n          }\r\n        }\r\n      } catch (error) {\r\n        console.error(`💥 Final connection error: ${error.message}`);\r\n        this.isConnected = false;\r\n        if (!this.isReconnecting) {\r\n          console.log('⏳ Scheduling reconnection attempt in 5 seconds...');\r\n          this.isReconnecting = true;\r\n          setTimeout(() => this.connect(), _data_definitions_js__WEBPACK_IMPORTED_MODULE_0__.reconnectionDelay);\r\n        }\r\n      }\r\n    },\r\n\r\n    startPresencePolling(xmppConnection) {\r\n      this.presenceInterval = setInterval(async () => {\r\n        if (!this.isConnected) {\r\n          console.log('⚠️ Skipping presence poll - not connected');\r\n          return;\r\n        }\r\n        try {\r\n          const xmlResponse = await xmppConnection.sendRequestWithRetry(\r\n            `<body rid='${xmppConnection.nextRid()}' sid='${xmppConnection.sid}' xmlns='http://jabber.org/protocol/httpbind'/>`\r\n          );\r\n          safeUpdatePresence(xmlResponse);\r\n          safeProcessMessages(xmlResponse);\r\n          this.isReconnecting = false;\r\n        } catch (error) {\r\n          console.error('Presence polling error:', error.message);\r\n          if (error.message.includes('404') && !this.isReconnecting) {\r\n            console.log('🛑 Connection lost (404). Reconnecting in 5 seconds...');\r\n            (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__.showChatAlert)(\"Chat connection lost. Reconnecting...\", { type: 'warning' });\r\n            messageManager.refreshMessages(false);\r\n            this.isReconnecting = true;\r\n            this.isConnected = false;\r\n            clearInterval(this.presenceInterval);\r\n            this.presenceInterval = null;\r\n            setTimeout(() => this.connect(), _data_definitions_js__WEBPACK_IMPORTED_MODULE_0__.reconnectionDelay);\r\n          }\r\n        }\r\n      }, _data_definitions_js__WEBPACK_IMPORTED_MODULE_0__.userListDelay);\r\n    },\r\n\r\n    sendMessage(text) {\r\n      const messageId = `msg_${Date.now()}`;\r\n      let isPrivate = false;\r\n      let fullJid = null;\r\n      let recipient = null;\r\n\r\n      // Only mark as pending if we're disconnected\r\n      const isPending = !this.isConnected || this.isReconnecting;\r\n\r\n      if (_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__.privateMessageState.isPrivateMode && _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__.privateMessageState.fullJid) {\r\n        isPrivate = true;\r\n        fullJid = _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__.privateMessageState.fullJid;\r\n        recipient = _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__.privateMessageState.targetUsername;\r\n        messageManager.addSentMessage(text, {\r\n          isPrivate: true,\r\n          recipient,\r\n          pending: isPending\r\n        });\r\n      } else {\r\n        messageManager.addSentMessage(text, { pending: isPending });\r\n      }\r\n\r\n      const now = Date.now();\r\n      const debounceTime = 2000; // 2 seconds to prevent duplicates\r\n\r\n      // Check against the last sent message.\r\n      if (this.lastSentMessage && this.lastSentMessage.text === text && (now - this.lastSentMessage.timestamp) < debounceTime) {\r\n        console.log('Duplicate message prevented:', text);\r\n        return;\r\n      }\r\n\r\n      // Update lastSentMessage info.\r\n      this.lastSentMessage = {\r\n        text,\r\n        timestamp: now\r\n      };\r\n\r\n      // Enqueue the message with timestamp.\r\n      this.messageQueue.push({\r\n        text,\r\n        id: messageId,\r\n        isPrivate,\r\n        fullJid,\r\n        recipient,\r\n        pending: isPending,\r\n        enqueueTime: now\r\n      });\r\n\r\n      // Process the queue on connection establishment.\r\n      if (this.isConnected && !this.isReconnecting) {\r\n        this.processQueue();\r\n      }\r\n    }\r\n\r\n  };\r\n\r\n  // --- Network connectivity handling ---\r\n  // Listen for offline events to stop presence polling.\r\n  window.addEventListener('offline', () => {\r\n    console.log(\"Network offline. Stopping presence polling.\");\r\n    if (xmppClient.presenceInterval) {\r\n      clearInterval(xmppClient.presenceInterval);\r\n      xmppClient.presenceInterval = null;\r\n    }\r\n    xmppClient.isConnected = false;\r\n  });\r\n\r\n  // Listen for online events to attempt reconnection.\r\n  window.addEventListener('online', async () => {\r\n    console.log(\"Network online. Scheduling reconnection in 5 seconds...\");\r\n    await (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_2__.sleep)(5000);\r\n    if (!xmppClient.isConnected && !xmppClient.isReconnecting) {\r\n      xmppClient.connect();\r\n    }\r\n  });\r\n  // --- End network handling ---\r\n\r\n  return xmppClient;\r\n}\r\n\n\n//# sourceURL=webpack://tampermonkey-script/./src/xmpp/xmppClient.js?");

/***/ }),

/***/ "./src/xmpp/xmppConnection.js":
/*!************************************!*\
  !*** ./src/xmpp/xmppConnection.js ***!
  \************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (/* binding */ XMPPConnection)\n/* harmony export */ });\n/* harmony import */ var _data_definitions_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../data/definitions.js */ \"./src/data/definitions.js\");\n/* harmony import */ var _helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../helpers/helpers.js */ \"./src/helpers/helpers.js\");\n\r\n\r\n\r\nclass XMPPConnection {\r\n  constructor({ username, password, bindUrl, delay = _data_definitions_js__WEBPACK_IMPORTED_MODULE_0__.connectionDelay }) {\r\n    this.username = username;\r\n    this.password = password;\r\n    this.bindUrl = bindUrl;\r\n    this.delay = delay;\r\n    this.sid = null;\r\n    this.rid = Math.floor(Date.now() / 1000);\r\n  }\r\n\r\n  nextRid() {\r\n    return ++this.rid;\r\n  }\r\n\r\n  async sendRequest(payload) {\r\n    const response = await fetch(this.bindUrl, {\r\n      method: 'POST',\r\n      headers: {\r\n        'Content-Type': 'text/xml; charset=UTF-8',\r\n        'User-Agent': 'Mozilla/5.0'\r\n      },\r\n      body: payload\r\n    });\r\n    if (!response.ok) {\r\n      throw new Error(`HTTP error! status: ${response.status}`);\r\n    }\r\n    return await response.text();\r\n  }\r\n\r\n  async sendRequestWithRetry(payload, maxRetries = 5) {\r\n    let lastError;\r\n    let baseWaitTime = this.delay;\r\n    for (let attempt = 1; attempt <= maxRetries; attempt++) {\r\n      try {\r\n        return await this.sendRequest(payload);\r\n      } catch (error) {\r\n        lastError = error;\r\n        if (error.message.includes('429')) {\r\n          const waitTime = baseWaitTime * Math.pow(2, attempt);\r\n          console.log(`⏱️ Rate limited (attempt ${attempt}/${maxRetries}). Waiting ${waitTime}ms...`);\r\n          await (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.sleep)(waitTime);\r\n        } else {\r\n          throw error;\r\n        }\r\n      }\r\n    }\r\n    throw new Error(`Max retries reached. Last error: ${lastError.message}`);\r\n  }\r\n\r\n  async connect() {\r\n\r\n    console.log('🌐 Step 1: Connecting to XMPP server...');\r\n\r\n    const initPayload = `<body xmlns='http://jabber.org/protocol/httpbind'\r\n               rid='${this.nextRid()}'\r\n               to='jabber.klavogonki.ru'\r\n               xml:lang='en'\r\n               wait='60'\r\n               hold='1'\r\n               ver='1.6'\r\n               xmpp:version='1.0'\r\n               xmlns:xmpp='urn:xmpp:xbosh'/>`;\r\n    const initResponse = await this.sendRequestWithRetry(initPayload);\r\n    this.sid = initResponse.match(/sid=['\"]([^'\"]+)['\"]/)[1];\r\n\r\n    console.log(`🔑 Step 2: Session ID received: ${this.sid}`);\r\n\r\n    await (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.sleep)(this.delay);\r\n\r\n    console.log('🔐 Step 3: Authenticating...');\r\n\r\n    const authString = (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.base64Encode)('\\x00' + this.username + '\\x00' + this.password);\r\n    const authPayload = `<body rid='${this.nextRid()}' sid='${this.sid}'\r\n               xmlns='http://jabber.org/protocol/httpbind'>\r\n          <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>${authString}</auth>\r\n        </body>`;\r\n    const authResponse = await this.sendRequestWithRetry(authPayload);\r\n    if (!authResponse.includes('<success')) {\r\n      throw new Error('❌ Authentication failed');\r\n    }\r\n    console.log('✅ Step 4: Authentication successful!');\r\n\r\n    await (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.sleep)(this.delay);\r\n\r\n    console.log('🔄 Step 5: Restarting stream...');\r\n\r\n    const restartPayload = `<body rid='${this.nextRid()}' sid='${this.sid}'\r\n               xmlns='http://jabber.org/protocol/httpbind'\r\n               to='jabber.klavogonki.ru'\r\n               xmpp:restart='true'\r\n               xmlns:xmpp='urn:xmpp:xbosh'/>`;\r\n    await this.sendRequestWithRetry(restartPayload);\r\n\r\n    await (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.sleep)(this.delay);\r\n\r\n    console.log('📦 Step 6: Binding resource...');\r\n\r\n    const bindPayload = `<body rid='${this.nextRid()}' sid='${this.sid}'\r\n               xmlns='http://jabber.org/protocol/httpbind'>\r\n          <iq type='set' id='bind_1' xmlns='jabber:client'>\r\n            <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>\r\n              <resource>web</resource>\r\n            </bind>\r\n          </iq>\r\n        </body>`;\r\n    await this.sendRequestWithRetry(bindPayload);\r\n\r\n    await (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.sleep)(this.delay);\r\n\r\n    console.log('🔌 Step 7: Establishing session...');\r\n\r\n    const sessionPayload = `<body rid='${this.nextRid()}' sid='${this.sid}'\r\n               xmlns='http://jabber.org/protocol/httpbind'>\r\n          <iq type='set' id='session_1' xmlns='jabber:client'>\r\n            <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>\r\n          </iq>\r\n        </body>`;\r\n    await this.sendRequestWithRetry(sessionPayload);\r\n\r\n    await (0,_helpers_helpers_js__WEBPACK_IMPORTED_MODULE_1__.sleep)(this.delay);\r\n\r\n    // Return session details for further use.\r\n    return { sid: this.sid, rid: this.rid };\r\n  }\r\n}\n\n//# sourceURL=webpack://tampermonkey-script/./src/xmpp/xmppConnection.js?");

/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			id: moduleId,
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
/******/ 	/* webpack/runtime/compat get default export */
/******/ 	(() => {
/******/ 		// getDefaultExport function for compatibility with non-harmony modules
/******/ 		__webpack_require__.n = (module) => {
/******/ 			var getter = module && module.__esModule ?
/******/ 				() => (module['default']) :
/******/ 				() => (module);
/******/ 			__webpack_require__.d(getter, { a: getter });
/******/ 			return getter;
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/define property getters */
/******/ 	(() => {
/******/ 		// define getter functions for harmony exports
/******/ 		__webpack_require__.d = (exports, definition) => {
/******/ 			for(var key in definition) {
/******/ 				if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ 					Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ 				}
/******/ 			}
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/hasOwnProperty shorthand */
/******/ 	(() => {
/******/ 		__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/make namespace object */
/******/ 	(() => {
/******/ 		// define __esModule on exports
/******/ 		__webpack_require__.r = (exports) => {
/******/ 			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 				Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 			}
/******/ 			Object.defineProperty(exports, '__esModule', { value: true });
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/nonce */
/******/ 	(() => {
/******/ 		__webpack_require__.nc = undefined;
/******/ 	})();
/******/ 	
/************************************************************************/
/******/ 	
/******/ 	// startup
/******/ 	// Load entry module and return exports
/******/ 	// This entry module can't be inlined because the eval devtool is used.
/******/ 	var __webpack_exports__ = __webpack_require__("./main.js");
/******/ 	
/******/ })()
;