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);
        });
    });
})();