BR Forum Assistant

Стильный интерфейс для работы с шаблонами ответов, быстрая смена префиксов, предпросмотр ответов

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         BR Forum Assistant
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Стильный интерфейс для работы с шаблонами ответов, быстрая смена префиксов, предпросмотр ответов
// @author       Valik
// @match        https://forum.blackrussia.online/threads/*
// @icon         https://i.postimg.cc/bJQpMWfY/image.png
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // ========== Префиксы ==========
    const PREFIX = {
        NONE: 0,
        ACCEPT: 8,
        UNACCEPT: 4,
        PIN: 2,
        SPECADM: 11,
        GA: 12,
        CLOSE: 7,
        TEXY: 13,
        REALIZOVANO: 5,
        VAJNO: 1,
        OJIDANIE: 14,
        RASSMOTRENO: 9,
        KACHESTVO: 15,
    };

    // ========== БАЗА ВСЕХ ШАБЛОНОВ (полная, из вашего скрипта) ==========
    const rawButtons = [
        { title: 'Приветсвие', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>текст <br><br>Закрыто. [/FONT][/SIZE]' },
        { title: 'Ссылку на вк', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Приложите в следующей теме ссылку на вашу VK Страницу. <br><br>Закрыто. [/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Свяжитесь со мной', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Свяжитесь со мной вконтакте: https://vk.com/77oleinik77<br><br>На рассмотрении.[/FONT]', prefix: PREFIX.PIN, sticky: true },
        { title: 'ᅠ ᅠ ᅠ ᅠ  ᅠ ᅠ ᅠ ᅠ     ᅠ ᅠ ᅠᅠ|-(--(-(-> Жалобы на администрацию <-)-)--)-|ᅠ ᅠ ᅠ    ᅠ ᅠᅠ ᅠ ᅠ ᅠ   ᅠ ᅠ ᅠᅠ' },
        { title: 'На рассмотрении', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваша жалоба принята на рассмотрение. Ожидайте ответа. Просим не создавать дубликаты темы.<br><br>Ожидайте ответа.[/FONT][/SIZE]', prefix: PREFIX.PIN, sticky: true },
        { title: 'Ссылку на тему', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Приложите, пожалуйста, ссылку на тему в следующем обращении.<br><br>Отказано. Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Жалоба составлена не по форме', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваша жалоба составлена не по форме. Ознакомьтесь с правилами подачи жалобы: [URL=\'https://forum.blackrussia.online/index.php?threads/Правила-подачи-жалоб-на-администрацию.3429349/\']*Клик*[/URL]<br><br>Название темы: Nick_Name администратора | Суть жалобы<br><br>Форма:<br>[ICODE]1. Ваш Nick_Name:<br>2. Nick_Name администратора:<br>3. Дата:<br>4. Суть жалобы:<br>5. Доказательства:[/ICODE]<br><br>Отказано. Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Запрос доказательств', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>У администратора были запрошены доказательства. Ожидайте, пожалуйста, ответа.<br><br>На рассмотрении.[/FONT][/SIZE]', prefix: PREFIX.PIN, sticky: true },
        { title: 'Наказание выдано верно', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Доказательства предоставлены. Наказание выдано верно.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Отсутствуют доказательства', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Приложите доказательства в следующей теме.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Нерабочие доказательства', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Вы приложили нерабочие доказательства.<br>В следующей теме приложите рабочие доказательства в виде ссылки на фотохостинг.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Ошибка администратора', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Администратор допустил ошибку. Приносим извинения. Наказание снято.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.ACCEPT, sticky: false },
        { title: 'Беседа с администратором', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>С администратором будет проведена профилактическая беседа.<br><br>Решено.[/FONT][/SIZE]', prefix: PREFIX.ACCEPT, sticky: false },
        { title: 'Передача Главному Администратору', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваша жалоба передана Главному Администратору.<br><br>Ожидайте ответа.[/FONT][/SIZE]', prefix: PREFIX.GA, sticky: true },
        { title: 'Передача Спец. Администрации', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваша жалоба передана Специальной Администрации.<br><br>Ожидайте ответа.[/FONT][/SIZE]', prefix: PREFIX.SPECADM, sticky: true },
        { title: 'В тех раздел', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваша тема не относится к жалобам на администрацию. Пожалуйста, обратитесь в технический раздел форума.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Жалоба от третьего лица', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Жалоба подана от третьего лица и не подлежит рассмотрению.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Отсутствует /time', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>В представленном доказательстве отсутствует /time.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Срок подачи жалобы истёк', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>С момента выдачи наказания прошло более 48 часов. Жалоба не подлежит рассмотрению.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Обжалование наказаний', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваша жалоба относится к обжалованию наказания. Обратитесь, пожалуйста, в соответствующий раздел.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Недостаточно доказательств', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Недостаточно доказательств нарушения со стороны администратора.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ   ᅠ ᅠ ᅠᅠ|-(--(-(-> Обжалование наказаний <-)-)--)-| ᅠ ᅠ ᅠ  ᅠ   ᅠ   ᅠ ᅠ ᅠ ᅠᅠ ᅠ ᅠᅠᅠ' },
        { title: 'Обжалование на рассмотрении', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваше обжалование взято на рассмотрение. Не нужно создавать копии этой темы.<br><br>Ожидайте ответа.[/FONT]', prefix: PREFIX.GA, sticky: true },
        { title: 'Передача ГА', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваше обжалование передано Главному Администратору.<br><br>Ожидайте ответа.[/FONT][/SIZE]', prefix: PREFIX.GA, sticky: true },
        { title: 'Передача СА', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваше обжалование передано Специальной Администрации.<br><br>Ожидайте ответа.[/FONT][/SIZE]', prefix: PREFIX.SPECADM, sticky: true },
        { title: 'В жалобы на админов', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Переношу вашу тему в раздел жалоб на администрацию.<br><br>Отказано.[/FONT][/SIZE]', prefix: PREFIX.OJIDANIE, sticky: false },
        { title: 'Взлом', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Приложите скриншоты привязок аккаунта в следующей теме.<br><br>Отказано.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'В жалобы на тех', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Вам было выдано наказание от технического специалиста, обратитесь в раздел "Жалобы на технических специалистов" нашего сервера.<br><br>Закрыто.[/FONT]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Жалобы от 3-его лица', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Обжалование составлено от 3-го лица.<br><br>Закрыто.[/FONT]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Присутвуют редактирования', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Доказательства должны быть в первоначальном виде, без присутствия редактирования с помощью сторонних программ.<br><br>Закрыто.[/FONT]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Нет окна блокировки', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Без окна блокировки тема не подлежит рассмотрению. Пожалуйста, создайте новую тему и приложите окно блокировки с фотохостинга.<br>[URL="https://yapx.ru/"]yapx.ru[/URL],<br>[URL="https://imgur.com/"]imgur.com[/URL],<br>[URL="https://www.youtube.com/"]youtube.com[/URL],<br>[URL="https://imgbb.com"]ImgBB.com[/URL]<br>(все кликабельно).<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Обжалование не по форме', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваше обжалование составлено не по форме. Пожалуйста создайте новую тему, соблюдая форму подачи:<br>Ваш никнейм и причина блокировки, пример:<br>Bruce_Banner | Массовый DM.<br>и форму обжалований:<br>[ICODE]1. Ваш Nick_Name:<br>2. Nick_Name администратора:<br>3. Дата выдачи/получения наказания:<br>4. Суть заявки:<br>5. Доказательство:[/ICODE]<br><br>Закрыто.[/FONT]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Доква с соц сетей', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Доказательства с соц.сетей, не принимаются.<br>Загрузите их на фото-хостинг [URL="https://yapx.ru/"]yapx.ru[/URL], [URL="https://imgur.com/"]imgur.com[/URL], [URL="https://www.youtube.com/"]youtube.com[/URL],[URL="https://imgbb.com"]ImgBB.com[/URL](все кликабетильно).<br><br>Закрыто.[/FONT]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'В другой раздел', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Созданная тема никоим образом не относится к данному разделу.<br><br>Закрыто.[/FONT]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Бан айпи', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Для решения проблемы, пожалуйста, воспользуйтесь VPN или смените сеть Wi-Fi.<br><br>Закрыто.[/FONT]', prefix: PREFIX.CLOSE, sticky: false },
        { title: 'Дубликат', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ранее вам уже был дан ответ в подобной теме.<br>Если вы продолжите создавать дубликаты, ваш форумный аккаунт будет заблокирован.<br><br>Закрыто.[/FONT]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'НонРП Обман (свяжитесь)', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Если вы готовы возместить ущерб игроку, свяжитесь с игроком для возврата имущества, затем он должен оформить обжалование.<br><br>Закрыто.[/FONT]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'НонРП Обман (срок вышел)', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>У вас было 24 часа на проведение сделки и возмещение ущерба, а также предоставление записи (Fraps).<br>Срок истёк, однако фрапс так и не был предоставлен.<br>В связи с этим аккаунт будет заблокирован.<br><br>Закрыто.[/FONT]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'НонРП Обман (даю 24 часа)', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Аккаунт разблокирован.<br>У вас есть 24 часа для проведения сделки и возмещения ущерба.<br>После этого обязательно предоставьте запись (Fraps) в эту тему.<br><br>Закрыто.[/FONT]', prefix: PREFIX.GA, sticky: true },
        { title: 'Наказание снято', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Рассмотрев ваше обжалование, было принято решение о снятии вашего наказания.<br><br>Одобрено, закрыто.[/FONT][/SIZE]', prefix: PREFIX.ACCEPT, sticky: false },
        { title: 'ЧС Сервера', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Вы были вынесены из Чёрного Списка Сервера.<br><br>Одобрено.[/FONT][/SIZE]', prefix: PREFIX.ACCEPT, sticky: false },
        { title: 'ОЧСА', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Общий Чёрный Список Администрации снят.<br><br>Одобрено.[/FONT][/SIZE]', prefix: PREFIX.ACCEPT, sticky: false },
        { title: 'ОЧСП', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Общий Чёрный Список Проекта снят.<br><br>Одобрено.[/FONT][/SIZE]', prefix: PREFIX.ACCEPT, sticky: false },
        { title: 'ЧСДП', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Глобальная блокировка снята.<br><br>Одобрено.[/FONT][/SIZE]', prefix: PREFIX.ACCEPT, sticky: false },
        { title: 'Блокировка аккаунта', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Блокировка аккаунта снята.<br><br>Одобрено.[/FONT][/SIZE]', prefix: PREFIX.ACCEPT, sticky: false },
        { title: 'ранее был обжалован', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Вам уже было одобрено обжалование.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Обжалованию не подлежит', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваше обжалование не будет рассматриваться и будет закрыто, так как ваше наказание соответствует причинам, которые обжалованию не подлежат: различные формы слива, продажа игровой валюты, махинации, целенаправленный багоюз, продажа, передача аккаунта, сокрытие ошибок, багов системы, использование стороннего программного обеспечения, распространение конфиденциальной информации, обман администрации.<br><br>Отказано.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Отказано', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>В обжаловании отказано.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Наказание сокращено', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваше обжалование было рассмотрено и принято решение о сокращении вашего наказания.<br><br>Одобрено.[/FONT][/SIZE]', prefix: PREFIX.ACCEPT, sticky: false },
        { title: 'Server', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Спасибо за обращение, блокировка снята.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.RASSMOTRENO, sticky: false },
        { title: 'Слив админки (бан)', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Спасибо за обращение, администратор был снят с поста и занесён в Общий чёрный список проекта. Блокировка снята. <br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.RASSMOTRENO, sticky: false },
        { title: 'Слив админки (мут)', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Спасибо за обращение, администратор был снят с поста и занесён в Общий чёрный список проекта. Блокировка чата снята. <br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.RASSMOTRENO, sticky: false },
        { title: 'Слив админки (варн)', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Спасибо за обращение, администратор был снят с поста и занесён в Общий чёрный список проекта. Предупреждение снято. <br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.RASSMOTRENO, sticky: false },
        { title: 'Смена ника', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Аккаунт разблокирован. У вас есть 24 часа на смену игрового никнейма. После смены, пожалуйста, предоставьте скриншот в этой теме. Тема остаётся открытой.<br><br>На рассмотрении.[/FONT][/SIZE]', prefix: PREFIX.GA, sticky: true },
        { title: 'Не сменил ник', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Ваш аккаунт был заблокирован. У вас было 24 часа на смену игрового никнейма, а также необходимо было приложить скриншот, подтверждающий смену никнейма, в данную тему.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'ᅠ ᅠ ᅠ ᅠ ᅠ ᅠ   ᅠ ᅠ ᅠᅠ ᅠ ᅠ ᅠᅠ|-(--(-(-> Жалобы на игроков <-)-)--)-| ᅠ ᅠ ᅠ  ᅠ      ᅠ ᅠ ᅠ  ᅠ ᅠᅠ   ᅠᅠ ᅠ ᅠᅠᅠ' },
        { title: 'Игрок будет наказан', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Игрок получит соответсвующее наказание.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.ACCEPT, sticky: false },
        { title: 'срок 72 часа', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Срок написания жалобы составляет три дня (72 часа) с момента совершенного нарушения со стороны игрока сервера. В случае истечения срока жалоба рассмотрению не подлежит.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.ACCEPT, sticky: false },
        { title: 'Нужен фрапс', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br>Для рассмотрения данного нарушения требуется предоставить видеозапись, на которой чётко зафиксировано нарушение со стороны игрока.<br><br>Закрыто.[/FONT][/SIZE]', prefix: PREFIX.UNACCEPT, sticky: false },
        { title: 'Не по форме', content: '[SIZE=4][FONT=Verdana]Здравствуйте.<br><br> Ваша жалоба составлена не по форме. Пожалуйста создайте новую тему соблюдая форму подачи:<br>Ваш никнейм и никнейм нарушителя, пример:<br>Timofei_Oleinik | OOC Обман.<br>и форму жалоб на игроков:<br>[ICODE]1. Ваш Nick_Name:<br>2. Nick_Name игрока:<br>3. Суть жалобы:<br>4. Доказательство:[/ICODE]<br><br>Закрыто.[/FONT]', prefix: PREFIX.UNACCEPT, sticky: false },
    ];

    // ========== Группировка по категориям ==========
    function buildTemplateGroups() {
        const groups = [];
        let currentCategory = 'Основные';
        let currentItems = [];
        for (const btn of rawButtons) {
            if (btn.title.includes('|-(--(-(->') || (btn.title.includes('ᅠ') && btn.title.length > 20)) {
                if (currentItems.length) groups.push({ category: currentCategory, items: [...currentItems] });
                currentItems = [];
                const match = btn.title.match(/->\s*(.+?)\s*<-/);
                currentCategory = match ? match[1].trim() : btn.title.replace(/[ᅠ\s\-|()]+/g, ' ').trim();
                continue;
            }
            currentItems.push({
                title: btn.title,
                icon: getIconForTitle(btn.title),
                statusBadge: getStatusBadge(btn.prefix),
                content: btn.content,
                prefix: btn.prefix,
                sticky: btn.sticky || false,
            });
        }
        if (currentItems.length) groups.push({ category: currentCategory, items: currentItems });
        return groups;
    }

    function getIconForTitle(title) {
        if (title.includes('жалоб') || title.includes('Жалоб')) return '⚠️';
        if (title.includes('Обжалование')) return '⚖️';
        if (title.includes('Привет')) return '👋';
        if (title.includes('Свяжитесь')) return '📞';
        if (title.includes('Ссылку')) return '🔗';
        if (title.includes('Наказание')) return '🔨';
        if (title.includes('Ошибка')) return '❌';
        if (title.includes('Слив')) return '💧';
        if (title.includes('Смена')) return '✏️';
        return '📌';
    }

    function getStatusBadge(prefix) {
        if (prefix === PREFIX.ACCEPT) return { text: 'Одобрено', color: '#2e7d32' };
        if (prefix === PREFIX.UNACCEPT) return { text: 'Отказано', color: '#c62828' };
        if (prefix === PREFIX.PIN) return { text: 'На рассмотрении', color: '#ed6c02' };
        if (prefix === PREFIX.GA || prefix === PREFIX.SPECADM) return { text: 'Передано', color: '#0288d1' };
        if (prefix === PREFIX.CLOSE) return { text: 'Закрыто', color: '#6c757d' };
        if (prefix === PREFIX.REALIZOVANO) return { text: 'Реализовано', color: '#0097a7' };
        if (prefix === PREFIX.RASSMOTRENO) return { text: 'Рассмотрено', color: '#558b2f' };
        return null;
    }

    let templateGroups = [];

    // ========== Вспомогательные функции ==========
    function escapeHtml(str) {
        if (!str) return '';
        return str.replace(/[&<>]/g, (m) => (m === '&' ? '&amp;' : m === '<' ? '&lt;' : '&gt;'));
    }

    function getThreadData() {
        const authorID = $('a.username').first().data('user-id') || $('a.username').attr('data-user-id');
        const authorName = $('a.username').first().text().trim();
        const hours = new Date().getHours();
        let greeting = 'Доброй ночи';
        if (hours > 4 && hours <= 11) greeting = 'Доброе утро';
        else if (hours > 11 && hours <= 15) greeting = 'Добрый день';
        else if (hours > 15 && hours <= 21) greeting = 'Добрый вечер';
        return { user: { id: authorID, name: authorName, mention: `[USER=${authorID}]${authorName}[/USER]` }, greeting };
    }

    async function postReply(content) {
        return new Promise((resolve, reject) => {
            const $form = $('form[data-xf-post="quick-reply"]');
            if (!$form.length) return reject('Форма быстрого ответа не найдена');
            $form.find('textarea[name=message_html], .fr-element').html(content);
            if (window.XFEditor && window.XFEditor.setContent) window.XFEditor.setContent(content);
            else $form.find('textarea[name=message]').val(content);
            const formData = new FormData($form[0]);
            formData.append('_xfToken', XF.config.csrf);
            formData.append('_xfResponseType', 'json');
            fetch($form.attr('action'), { method: 'POST', body: formData, credentials: 'same-origin' })
                .then(r => r.json())
                .then(data => data.status === 'ok' ? resolve(data) : reject(data.errors || 'Ошибка отправки'))
                .catch(reject);
        });
    }

    function editThreadData(prefix, pin = false) {
        const threadTitle = $('.p-title-value').last().text().trim();
        const formData = new FormData();
        formData.append('prefix_id', prefix);
        formData.append('title', threadTitle);
        if (pin) formData.append('sticky', 1);
        formData.append('_xfToken', XF.config.csrf);
        formData.append('_xfRequestUri', document.URL.split(XF.config.url.fullBase)[1]);
        formData.append('_xfWithData', 1);
        formData.append('_xfResponseType', 'json');
        fetch(`${document.URL}edit`, { method: 'POST', body: formData }).then(() => location.reload());
    }

    async function applyTemplateById(groupIdx, itemIdx, closeModal = true) {
        const item = templateGroups[groupIdx]?.items[itemIdx];
        if (!item) return;
        const compiled = Handlebars.compile(item.content);
        const content = compiled(getThreadData());
        $('.fr-element.fr-view').html(content);
        $('span.fr-placeholder').empty();
        await postReply(content);
        if (item.prefix !== undefined) editThreadData(item.prefix, item.sticky || false);
        else location.reload();
        if (closeModal) {
            $('#tp-modal').hide();
            $('#tp-overlay').hide();
        }
    }

    // ========== Построение модального окна ==========
    function buildModalHTML() {
        const groupsHtml = templateGroups.map((g, gi) => {
            const itemsHtml = g.items.map((item, ii) => {
                const statusHtml = item.statusBadge ? `<span class="tp-card-status" style="background:${item.statusBadge.color}">${item.statusBadge.text}</span>` : '';
                return `
                    <div class="tp-card" data-group="${gi}" data-item="${ii}">
                        <div class="tp-card-icon">${item.icon || '📌'}</div>
                        <div class="tp-card-title">${escapeHtml(item.title)}</div>
                        ${statusHtml}
                        <div class="tp-card-buttons">
                            <button class="tp-card-preview-btn">👁️ Просмотр</button>
                            <button class="tp-card-send-btn">📤 Отправить</button>
                        </div>
                    </div>
                `;
            }).join('');
            return `<div class="tp-group"><div class="tp-group-header">${escapeHtml(g.category)}</div><div class="tp-card-grid">${itemsHtml}</div></div>`;
        }).join('');

        return `
            <div id="tp-overlay"></div>
            <div id="tp-modal">
                <div id="tp-header">
                    <div class="tp-header-title">
                        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#1e293b" stroke-width="1.5"><path d="M4 4H20V20H4V4Z M8 8H16V10H8V8Z M8 12H16V14H8V12Z M8 16H12V18H8V16Z"/></svg>
                        <span>Шаблоны ответов</span>
                    </div>
                    <button id="tp-close" class="tp-close-btn">✕</button>
                </div>
                <div id="tp-search-wrap">
                    <svg class="tp-search-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#64748b"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
                    <input type="text" id="tp-search" placeholder="Поиск...">
                </div>
                <div id="tp-groups-container">${groupsHtml}</div>
            </div>
        `;
    }

    // ========== ПРЕДПРОСМОТР (исправленный, гарантированно работающий) ==========
    let currentPreview = null;

    function showPreview(groupIdx, itemIdx) {
        if (currentPreview) closePreview();
        const item = templateGroups[groupIdx]?.items[itemIdx];
        if (!item) {
            console.warn('Шаблон не найден', groupIdx, itemIdx);
            return;
        }
        try {
            const compiled = Handlebars.compile(item.content);
            const rawContent = compiled(getThreadData());
            const displayContent = rawContent.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>');

            const $preview = $(`
                <div class="tp-preview-overlay">
                    <div class="tp-preview-card">
                        <div class="tp-preview-header">
                            <span>📄 Предпросмотр</span>
                            <button class="tp-preview-close">✕</button>
                        </div>
                        <div class="tp-preview-content">${displayContent}</div>
                        <div class="tp-preview-actions">
                            <button class="tp-preview-apply" data-group="${groupIdx}" data-item="${itemIdx}">Применить</button>
                            <button class="tp-preview-apply-close" data-group="${groupIdx}" data-item="${itemIdx}">Вставить и закрыть</button>
                        </div>
                    </div>
                </div>
            `);

            $('body').append($preview);
            currentPreview = $preview;

            // Центрирование
            $preview.css({ position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)' });

            // Закрытие по крестику или клику на фон
            $preview.find('.tp-preview-close').on('click', closePreview);
            $preview.on('click', (e) => {
                if (e.target === $preview[0]) closePreview();
            });

            // Кнопки внутри превью
            $preview.find('.tp-preview-apply, .tp-preview-apply-close').on('click', async function (e) {
                e.stopPropagation();
                const g = $(this).data('group');
                const i = $(this).data('item');
                await applyTemplateById(g, i, true);
                closePreview();
                $('#tp-modal').hide();
                $('#tp-overlay').hide();
            });
        } catch (err) {
            console.error('Ошибка при создании предпросмотра:', err);
        }
    }

    function closePreview() {
        if (currentPreview) {
            currentPreview.remove();
            currentPreview = null;
        }
    }

    // ========== Стили (минимализм, белый фон, анимации) ==========
    const style = document.createElement('style');
    style.textContent = `
        :root {
            --bg-white: #ffffff;
            --bg-light: #f8fafc;
            --border-light: #e2e8f0;
            --text-dark: #0f172a;
            --accent: #3b82f6;
            --btn-bg: #4b5563;
            --btn-hover: #374151;
        }
        @keyframes modalFadeIn {
            from { opacity: 0; transform: translate(-50%, -48%) scale(0.96); }
            to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
        }
        @keyframes cardFadeIn {
            from { opacity: 0; transform: translateY(8px); }
            to { opacity: 1; transform: translateY(0); }
        }
        @keyframes fadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
        }
        #tp-overlay {
            display: none; position: fixed; top:0; left:0; width:100%; height:100%;
            background: rgba(0,0,0,0.4);
            backdrop-filter: blur(3px);
            z-index: 9998;
            animation: fadeIn 0.2s ease;
        }
        #tp-modal {
            display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
            width: 85%; max-width: 750px; max-height: 85vh;
            background: var(--bg-white);
            border-radius: 28px;
            box-shadow: 0 25px 40px -12px rgba(0,0,0,0.25);
            z-index: 9999;
            padding: 20px 24px;
            font-family: 'Inter', system-ui, -apple-system, sans-serif;
            border: 1px solid var(--border-light);
            overflow: hidden;
            animation: modalFadeIn 0.2s ease;
        }
        #tp-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px; }
        .tp-header-title { display: flex; align-items: center; gap: 8px; font-size: 18px; font-weight: 600; color: var(--text-dark); }
        .tp-close-btn { background: none; border: none; color: #ef4444; font-size: 22px; cursor: pointer; transition: opacity 0.2s; }
        .tp-close-btn:hover { opacity: 0.7; }
        #tp-search-wrap { position: relative; margin-bottom: 20px; }
        .tp-search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); pointer-events: none; }
        #tp-search {
            width: 100%; padding: 10px 12px 10px 36px;
            background: var(--bg-light);
            border: 1px solid var(--border-light);
            border-radius: 40px;
            font-size: 13px;
            outline: none;
            transition: 0.2s;
        }
        #tp-search:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(59,130,246,0.2); }
        #tp-groups-container { overflow-y: auto; max-height: calc(85vh - 120px); padding-right: 4px; }
        .tp-group { margin-bottom: 24px; animation: cardFadeIn 0.2s ease; }
        .tp-group-header {
            font-size: 14px; font-weight: 600; margin-bottom: 12px; padding-bottom: 4px;
            border-bottom: 1.5px solid var(--border-light);
            color: var(--accent);
            text-transform: uppercase;
            letter-spacing: 0.3px;
        }
        .tp-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
        .tp-card {
            background: var(--bg-light);
            border: 1px solid var(--border-light);
            border-radius: 20px;
            padding: 14px;
            transition: all 0.25s cubic-bezier(0.2, 0, 0, 1);
            cursor: default;
        }
        .tp-card:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(0,0,0,0.08); border-color: var(--accent); }
        .tp-card-icon { font-size: 24px; margin-bottom: 8px; }
        .tp-card-title { font-weight: 500; font-size: 13px; margin-bottom: 10px; line-height: 1.4; color: var(--text-dark); }
        .tp-card-status { display: inline-block; padding: 2px 10px; border-radius: 30px; font-size: 10px; font-weight: 600; color: white; margin-bottom: 12px; }
        .tp-card-buttons { display: flex; gap: 8px; margin-top: 4px; }
        .tp-card-preview-btn, .tp-card-send-btn {
            flex: 1;
            background: transparent;
            border: 1px solid var(--border-light);
            border-radius: 40px;
            padding: 6px 0;
            font-size: 11px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s;
            text-align: center;
        }
        .tp-card-preview-btn { color: var(--accent); border-color: var(--accent); }
        .tp-card-preview-btn:hover { background: var(--accent); color: white; }
        .tp-card-send-btn { color: white; background: var(--btn-bg); border: none; }
        .tp-card-send-btn:hover { background: var(--btn-hover); transform: translateY(-1px); }
        .tp-preview-overlay {
            position: fixed; top:0; left:0; width:100%; height:100%;
            background: rgba(0,0,0,0.5); backdrop-filter: blur(4px); z-index: 10000;
            animation: fadeIn 0.15s ease;
        }
        .tp-preview-card {
            background: var(--bg-white); border-radius: 24px; width: 380px; max-height: 80vh;
            display: flex; flex-direction: column; box-shadow: 0 25px 40px -12px black;
            border: 1px solid var(--border-light); overflow: hidden;
            animation: modalFadeIn 0.15s ease;
        }
        .tp-preview-header {
            display: flex; justify-content: space-between; padding: 12px 18px;
            background: var(--bg-light); font-weight: 600; border-bottom: 1px solid var(--border-light);
            color: var(--text-dark);
        }
        .tp-preview-close { background: none; border: none; color: #ef4444; font-size: 20px; cursor: pointer; }
        .tp-preview-content {
    padding: 16px; overflow-y: auto; font-family: monospace; font-size: 12px;
    white-space: pre-wrap; word-wrap: break-word; background: #ffffff;
    color: #1e293b !important;
    max-height: 60vh;
            }
.tp-preview-content * {
    color: #1e293b !important;
}
            white-space: pre-wrap; word-wrap: break-word; background: white; max-height: 60vh;
        }
        .tp-preview-actions { padding: 12px 16px; display: flex; gap: 10px; border-top: 1px solid var(--border-light); background: var(--bg-light); }
        .tp-preview-actions button { flex: 1; padding: 6px 0; border-radius: 40px; border: none; font-weight: 500; font-size: 13px; cursor: pointer; transition: 0.2s; }
        .tp-preview-apply { background: var(--accent); color: white; }
        .tp-preview-apply-close { background: #f1f5f9; color: #1e293b; border: 1px solid #cbd5e1; }
        .tp-preview-apply:hover { background: #2563eb; }
        .tp-preview-apply-close:hover { background: #e2e8f0; }
        .pf-prefix-btn {
            background: #4b5563 !important;
            border: none !important;
            border-radius: 30px !important;
            padding: 4px 12px !important;
            font-size: 12px !important;
            font-weight: 500 !important;
            color: white !important;
            transition: all 0.2s !important;
            margin: 2px 3px !important;
            display: inline-flex !important;
            align-items: center !important;
            gap: 6px !important;
        }
        .pf-prefix-btn:hover {
            background: #374151 !important;
            transform: translateY(-1px);
        }
        #tp-open-btn {
            background: #4b5563 !important;
            border: none !important;
            border-radius: 30px !important;
            padding: 4px 12px !important;
            font-size: 12px !important;
            color: white !important;
            transition: 0.2s !important;
        }
        #tp-open-btn:hover {
            background: #374151 !important;
            transform: translateY(-1px);
        }
    `;
    document.head.appendChild(style);

    // ========== ИНИЦИАЛИЗАЦИЯ ==========
    $(document).ready(async () => {
        // Убедимся, что Handlebars загружен
        if (!window.Handlebars) {
            $('body').append('<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>');
            await new Promise(resolve => {
                const check = setInterval(() => {
                    if (window.Handlebars) {
                        clearInterval(check);
                        resolve();
                    }
                }, 100);
            });
        }

        // Построение групп шаблонов
        templateGroups = buildTemplateGroups();

        // Кнопки быстрых префиксов
        const btnConfig = [
            { text: '⏳ На рассмотрение', prefix: PREFIX.PIN, pin: true },
            { text: '❌ Отказано', prefix: PREFIX.UNACCEPT, pin: false },
            { text: '✅ Одобрено', prefix: PREFIX.ACCEPT, pin: false },
            { text: '🔥 Спецадмин', prefix: PREFIX.SPECADM, pin: true },
            { text: '🛠️ Тех.спец', prefix: PREFIX.TEXY, pin: false },
            { text: '👑 Гл.админ', prefix: PREFIX.GA, pin: true },
            { text: '🔒 Закрыто', prefix: PREFIX.CLOSE, pin: false },
            { text: '🎯 Реализовано', prefix: PREFIX.REALIZOVANO, pin: false },
            { text: '❗ Важно', prefix: PREFIX.VAJNO, pin: false },
            { text: '⏰ Ожидание', prefix: PREFIX.OJIDANIE, pin: false },
            { text: '✔️ Рассмотрено', prefix: PREFIX.RASSMOTRENO, pin: false },
            { text: '⚪ Без префикса', prefix: PREFIX.NONE, pin: false },
            { text: '🔍 Контроль качества', prefix: PREFIX.KACHESTVO, pin: false },
        ];
        btnConfig.forEach(cfg => {
            $('.button--icon--reply').before(
                `<button type="button" class="button rippleButton pf-prefix-btn" data-prefix="${cfg.prefix}" data-pin="${cfg.pin}" style="margin:2px;">${cfg.text}</button>`
            );
        });

        // Кнопка открытия модального окна
        $('.button--icon--reply').before('<button type="button" class="button rippleButton" id="tp-open-btn" style="margin:2px;">📋 Шаблоны</button>');

        $('body').append(buildModalHTML());
        const $modal = $('#tp-modal');
        const $overlay = $('#tp-overlay');
        const $search = $('#tp-search');
        const $groupsContainer = $('#tp-groups-container');

        $('#tp-open-btn').click(() => {
            $modal.show();
            $overlay.show();
            $search.val('').trigger('input');
            closePreview();
        });
        $('#tp-close, #tp-overlay').click(() => {
            $modal.hide();
            $overlay.hide();
            closePreview();
        });

        $search.on('input', function () {
            const q = this.value.trim().toLowerCase();
            $groupsContainer.find('.tp-group').each(function () {
                let visible = false;
                $(this).find('.tp-card').each(function () {
                    const txt = $(this).find('.tp-card-title').text().toLowerCase();
                    const match = txt.includes(q);
                    $(this).toggle(match);
                    if (match) visible = true;
                });
                $(this).toggle(visible);
            });
        });

        // Просмотр
        $(document).on('click', '.tp-card-preview-btn', function (e) {
            e.stopPropagation();
            const $card = $(this).closest('.tp-card');
            const g = $card.data('group');
            const i = $card.data('item');
            if (g !== undefined && i !== undefined) {
                showPreview(g, i);
            } else {
                console.warn('data-group или data-item не найдены', $card);
            }
        });

        // Отправить
        $(document).on('click', '.tp-card-send-btn', async function (e) {
            e.stopPropagation();
            const $card = $(this).closest('.tp-card');
            const g = $card.data('group');
            const i = $card.data('item');
            if (g !== undefined && i !== undefined) {
                await applyTemplateById(g, i, true);
                $modal.hide();
                $overlay.hide();
            }
        });

        // Кнопки префиксов
        $(document).on('click', '.pf-prefix-btn', function () {
            const prefix = parseInt($(this).data('prefix'));
            const pin = $(this).data('pin') === 'true';
            editThreadData(prefix, pin);
        });
    });
})();