// ==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");
/******/
/******/ })()
;