BB Quick fasReply (Admin)

Быстрые ответы (папки, поиск, закрепление) + мгновенные действия с темой + автообновление + настройки/темы оформления + дедлайн-таймер в списке тем

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         BB Quick fasReply (Admin)
// @namespace    https://forum.blackrussia.online
// @version      2.4.1
// @description  Быстрые ответы (папки, поиск, закрепление) + мгновенные действия с темой + автообновление + настройки/темы оформления + дедлайн-таймер в списке тем
// @match        https://forum.blackrussia.online/threads/*
// @match        https://forum.blackrussia.online/forums/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  const QUICK_ANSWERS_KEY = 'bb_quick_answers_v1';
  const QUICK_FOLDERS_KEY = 'bb_quick_folders_v1';
  const RECENT_ANSWERS_KEY = 'bb_recent_answers_v1';
  const HOME_PREFIX_KEY = 'bb_home_prefixes';
  const HOME_ACTIONS_KEY = 'bb_home_actions';
  const THEME_KEY = 'bb_theme_v1';
  const DEADLINE_CONFIG_KEY = 'bb_deadline_config_v1';
  const MAX_RECENT_SHOWN = 6;

  const FORUM_DEADLINES = {
    '435': { prefixes: { 0: 48, 2: 48, 14: 48 } },
    '433': { prefixes: { 0: 48, 2: 48, 14: 48 } },
    '434': { prefixes: { 0: 48, 2: 48, 14: 48 } },
    '436': { prefixes: { 0: 72, 2: 72, 14: 72 } },
    '415': { prefixes: { 0: 72, 2: 72, 14: 72 } },
    '416': { prefixes: { 0: 72, 2: 72, 14: 72 } },
    '413': { prefixes: { 0: 72, 2: 72, 14: 72 } },
  };

  const REPLY_BTN_SELECTOR =
    'button.button--primary.button--icon--reply, button.button--icon--reply, button.rippleButton.button--icon--reply';

  const PREFIX_MAP = [
    ['Важно', 1, 'label--red'],
    ['На рассмотрении', 2, 'label--orange'],
    ['Решено', 6, 'label--green'],
    ['Закрыто', 7, 'label--red'],
    ['Одобрено', 8, 'label--green'],
    ['Рассмотрено', 9, 'label--green'],
    ['Команде проекта', 10, 'label--yellow'],
    ['Специальному администратору', 11, 'label--accent'],
    ['Главному администратору', 12, 'label--red'],
    ['Тех. специалисту', 13, 'label--royalBlue'],
    ['Проверено контролем качества', 15, 'label--skyBlue'],
    ['Руководству модерации', 20, 'label--blue'],
    ['Реализовано', 5, 'label--green'],
    ['Отказано', 4, 'label--red'],
    ['Ожидание', 14, 'label--silver'],
  ];

  const THEMES = {
    dark:   { label: 'Тёмная',      bg: '#1a1e26', card: '#242a33', border: '#323a45', text: '#e8edf5', textDim: '#9aa5b5', accent: '#3f8bff' },
    light:  { label: 'Светлая',     bg: '#ffffff', card: '#f2f3f5', border: '#dcdfe4', text: '#15181d', textDim: '#6b7280', accent: '#2563eb' },
    blue:   { label: 'Синяя',       bg: '#0f1b2d', card: '#16273f', border: '#213a58', text: '#e8f0ff', textDim: '#8fa8c9', accent: '#4d9eff' },
    green:  { label: 'Зелёная',     bg: '#10201a', card: '#173226', border: '#234a37', text: '#e6fff2', textDim: '#8fc9a8', accent: '#33c17b' },
    purple: { label: 'Фиолетовая',  bg: '#1c1526', card: '#2a1f3d', border: '#3d2d57', text: '#f1e9ff', textDim: '#b39ddb', accent: '#9c6bff' },
    red:    { label: 'Красная',     bg: '#241213', card: '#331a1c', border: '#4a2529', text: '#ffecec', textDim: '#d99a9c', accent: '#ff5c5c' },
  };

  let bbLastQuickSettings = null;

  let bbOpenOverlayCount = 0;
  let bbqrWheelBlocker = null;
  let bbqrTouchBlocker = null;

  function bbqrCountOpenOverlays() {
    return document.querySelectorAll('.overlay-container[id^="bbqr"]').length;
  }

  function bbqrSyncOverlayLock() {
    const openCount = bbqrCountOpenOverlays();
    if (openCount > 0) {
      document.body.style.overflow = 'hidden';
      document.documentElement.style.overflow = 'hidden';
      if (!bbqrWheelBlocker) {
        bbqrWheelBlocker = (e) => { if (!e.target.closest('.bbqr-shell-body')) e.preventDefault(); };
        bbqrTouchBlocker = (e) => { if (!e.target.closest('.bbqr-shell-body')) e.preventDefault(); };
        window.addEventListener('wheel', bbqrWheelBlocker, { passive: false });
        window.addEventListener('touchmove', bbqrTouchBlocker, { passive: false });
      }
    } else {
      document.body.style.overflow = '';
      document.documentElement.style.overflow = '';
      if (bbqrWheelBlocker) {
        window.removeEventListener('wheel', bbqrWheelBlocker);
        window.removeEventListener('touchmove', bbqrTouchBlocker);
        bbqrWheelBlocker = null;
        bbqrTouchBlocker = null;
      }
    }
  }

  function trackOverlayOpen() { bbqrSyncOverlayLock(); }
  function trackOverlayClose() { bbqrSyncOverlayLock(); }

  function safeJsonParse(s) { try { return JSON.parse(s); } catch { return null; } }

  function genId() {
    return 'id_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
  }

  function escapeHtml(s) {
    return String(s ?? '')
      .replace(/&/g, '&')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#039;');
  }

  function loadQuickAnswers() {
    try {
      const raw = localStorage.getItem(QUICK_ANSWERS_KEY);
      const arr = raw ? safeJsonParse(raw) : [];
      return Array.isArray(arr) ? arr : [];
    } catch { return []; }
  }
  function saveQuickAnswers(list) {
    try { localStorage.setItem(QUICK_ANSWERS_KEY, JSON.stringify(list)); return true; } catch { return false; }
  }

  function loadQuickFolders() {
    try {
      const raw = localStorage.getItem(QUICK_FOLDERS_KEY);
      const arr = raw ? safeJsonParse(raw) : [];
      return Array.isArray(arr) ? arr : [];
    } catch { return []; }
  }
  function saveQuickFolders(list) {
    try { localStorage.setItem(QUICK_FOLDERS_KEY, JSON.stringify(list)); return true; } catch { return false; }
  }

  function loadRecentAnswers() {
    try {
      const raw = localStorage.getItem(RECENT_ANSWERS_KEY);
      const arr = raw ? safeJsonParse(raw) : [];
      return Array.isArray(arr) ? arr : [];
    } catch { return []; }
  }
  function saveRecentAnswers(list) {
    try { localStorage.setItem(RECENT_ANSWERS_KEY, JSON.stringify(list)); return true; } catch { return false; }
  }

  function loadHomePrefixes() {
    try {
      const raw = localStorage.getItem(HOME_PREFIX_KEY);
      const arr = raw ? JSON.parse(raw) : [];
      return Array.isArray(arr) ? new Set(arr) : new Set();
    } catch { return new Set(); }
  }
  function saveHomePrefixes(set) {
    try { localStorage.setItem(HOME_PREFIX_KEY, JSON.stringify([...set])); return true; } catch { return false; }
  }

  function loadHomeActions() {
    try {
      const raw = localStorage.getItem(HOME_ACTIONS_KEY);
      const arr = raw ? JSON.parse(raw) : [];
      return Array.isArray(arr) ? new Set(arr) : new Set();
    } catch { return new Set(); }
  }
  function saveHomeActions(set) {
    try { localStorage.setItem(HOME_ACTIONS_KEY, JSON.stringify([...set])); return true; } catch { return false; }
  }

  function loadTheme() {
    try { return localStorage.getItem(THEME_KEY) || 'dark'; } catch { return 'dark'; }
  }
  function saveTheme(key) {
    try { localStorage.setItem(THEME_KEY, key); return true; } catch { return false; }
  }

  function loadDeadlineConfig() {
    try {
      const raw = localStorage.getItem(DEADLINE_CONFIG_KEY);
      const obj = raw ? JSON.parse(raw) : {};
      return {
        enabled: true,
        ...obj
      };
    } catch { return { enabled: true }; }
  }
  function saveDeadlineConfig(cfg) {
    try { localStorage.setItem(DEADLINE_CONFIG_KEY, JSON.stringify(cfg)); return true; } catch { return false; }
  }

  function themeColors() {
    return THEMES[loadTheme()] || THEMES.dark;
  }

  function ensureDefaultFolder() {
    const folders = loadQuickFolders();
    let def = folders.find((f) => f.isDefault);
    if (!def) {
      def = { id: genId(), title: 'Общие', pinned: false, usageCount: 0, isDefault: true, createdAt: Date.now() };
      folders.push(def);
      saveQuickFolders(folders);
    }
    return def.id;
  }

  function migrateData() {
    const answers = loadQuickAnswers();
    let changed = false;
    let defaultFolderId = null;

    answers.forEach((a) => {
      if (!a.id) { a.id = genId(); changed = true; }
      if (!a.folderId) {
        if (!defaultFolderId) defaultFolderId = ensureDefaultFolder();
        a.folderId = defaultFolderId;
        changed = true;
      }
      if (typeof a.pinned !== 'boolean') { a.pinned = false; changed = true; }
      if (typeof a.usageCount !== 'number') { a.usageCount = 0; changed = true; }
    });

    if (changed) saveQuickAnswers(answers);

    const folders = loadQuickFolders();
    let foldersChanged = false;
    folders.forEach((f) => {
      if (typeof f.pinned !== 'boolean') { f.pinned = false; foldersChanged = true; }
    });
    if (foldersChanged) saveQuickFolders(folders);
  }

  function sortByPinAndUsage(list) {
    return [...list].sort((a, b) => {
      const pa = a.pinned ? 1 : 0, pb = b.pinned ? 1 : 0;
      if (pa !== pb) return pb - pa;
      const ua = a.usageCount || 0, ub = b.usageCount || 0;
      if (ua !== ub) return ub - ua;
      return (b.updatedAt || b.createdAt || 0) - (a.updatedAt || a.createdAt || 0);
    });
  }

  function setEditorMessageText(rawText) {
    const view = document.querySelector('div.fr-element.fr-view[contenteditable="true"], div.fr-element.fr-view');
    const text = String(rawText ?? '');

    if (view) {
      view.focus();
      const range = document.createRange();
      range.selectNodeContents(view);
      const sel = window.getSelection();
      sel.removeAllRanges();
      sel.addRange(range);

      try {
        document.execCommand('delete', false, null);
        const lines = text.split('\n');
        lines.forEach((line, idx) => {
          document.execCommand('insertText', false, line);
          if (idx < lines.length - 1) document.execCommand('insertText', false, '\n');
        });
      } catch (err) {
        console.warn('[BB Quick Reply] insertText не сработал, запасной путь:', err);
        view.textContent = '';
        const lines = text.split('\n');
        lines.forEach((line, idx) => {
          view.appendChild(document.createTextNode(line));
          if (idx < lines.length - 1) view.appendChild(document.createElement('br'));
        });
        view.dispatchEvent(new Event('input', { bubbles: true }));
      }
    }

    const $textarea = document.querySelector(
      'textarea[name="message_html"], textarea[data-xf-key="r"], textarea[name="message"]'
    );
    if ($textarea && $textarea.offsetParent !== null) {
      $textarea.value = text;
      $textarea.dispatchEvent(new Event('input', { bubbles: true }));
      $textarea.dispatchEvent(new Event('change', { bubbles: true }));
    }
  }

  function getFormDataObject(obj) {
    const fd = new FormData();
    Object.entries(obj).forEach(([k, v]) => { if (v !== undefined) fd.append(k, v); });
    return fd;
  }

  function getThreadTitle() {
    const t = document.querySelector('.p-title-value');
    if (!t) return null;
    return t.lastChild && t.lastChild.textContent ? t.lastChild.textContent : t.textContent;
  }

  function getThreadIdFromUrl() {
    const m = String(document.URL).match(/\/threads\/.+\.(\d+)(?:\/?$|[/?#])/i);
    return m ? m[1] : null;
  }

  function csrfToken() {
    return window.XF && XF.config && XF.config.csrf ? XF.config.csrf : null;
  }

  function getBaseThreadUrl() {
    return document.URL.replace(/\/(reply|page-\d+)?(\?.*)?(#.*)?$/i, '');
  }

  function findThreadToolLink(action) {
    const threadId = getThreadIdFromUrl();
    const candidates = Array.from(document.querySelectorAll(`a[href*="/${action}"]`)).filter(
      (a) => a.getAttribute('data-xf-click') === 'switch'
    );
    if (!threadId) return candidates[0];
    const scoped = candidates.find((a) => (a.getAttribute('href') || '').includes(`.${threadId}/${action}`));
    return scoped || candidates[0];
  }

  function findThreadToolLinkFallback(hrefSelector, textRegex) {
    const threadId = getThreadIdFromUrl();
    let candidates = Array.from(document.querySelectorAll(hrefSelector));
    if (threadId) {
      const scoped = candidates.filter((a) => (a.getAttribute('href') || '').includes(`.${threadId}/`));
      if (scoped.length) candidates = scoped;
    }
    return candidates.find((a) => textRegex.test(a.textContent)) || candidates[0];
  }

  function getThreadFlags() {
    let closeLink = findThreadToolLink('quick-close');
    let stickLink = findThreadToolLink('quick-stick');

    if (!closeLink) {
      closeLink = findThreadToolLinkFallback(
        'a[href*="quick-close"], a[href*="/close"], a[href*="/open"]',
        /открыть|закрыть/i
      );
    }
    if (!stickLink) {
      stickLink = findThreadToolLinkFallback(
        'a[href*="quick-stick"], a[href*="/stick"], a[href*="/unstick"]',
        /открепить|закрепить/i
      );
    }

    const isClosed = closeLink ? /Открыть/i.test(closeLink.textContent) : false;
    const isSticky = stickLink ? /Открепить/i.test(stickLink.textContent) : false;
    return { isSticky, isClosed, closeLink, stickLink };
  }

  function getThreadActionUrls() {
    const base = getBaseThreadUrl();
    const { isSticky, isClosed, closeLink, stickLink } = getThreadFlags();
    return {
      isSticky, isClosed,
      editUrl: `${base}/edit`,
      closeUrl: closeLink ? closeLink.getAttribute('href') : `${base}/quick-close`,
      stickUrl: stickLink ? stickLink.getAttribute('href') : `${base}/quick-stick`,
    };
  }

  function getCurrentForumId() {
    const m = location.pathname.match(/\/forums\/[^/]+\.(\d+)\//);
    if (m) return m[1];
    const urlParams = new URLSearchParams(location.search);
    const f = urlParams.get('f');
    if (f) return f;
    const crumb = document.querySelector('.p-breadcrumbs a[href*="/forums/"]');
    if (crumb) {
      const cm = crumb.getAttribute('href').match(/\/forums\/[^/]+\.(\d+)\//);
      if (cm) return cm[1];
    }
    return null;
  }

    function getPrefixIdFromLabelText(text) {
    if (!text || text.trim() === '') return 0; // Нет префикса = ID 0
    const found = PREFIX_MAP.find(p => p[0] === text.trim());
    return found ? found[1] : null;
  }

  function getThreadTimestampFromListing(row) {
    // ВАЖНО: точкой отсчёта дедлайна является время СОЗДАНИЯ темы
    // (structItem-startDate), а НЕ время последнего сообщения
    // (.structItem-cell--latest). Если считать от последней активности,
    // то у тем, в которых недавно писали, дедлайн всегда будет «зелёным»,
    // хотя срок уже мог пройти.
    const readTime = (el) => {
      if (!el) return null;
      const unix = el.getAttribute('data-time');
      if (unix) return Number(unix) * 1000;
      const dt = el.getAttribute('datetime');
      if (dt) {
        const d = new Date(dt);
        if (!isNaN(d)) return d.getTime();
      }
      return null;
    };

    // 1. Время создания темы (в ячейке заголовка, идёт «впереди» строки)
    const startTime = readTime(
      row.querySelector('.structItem-startDate time[data-time], .structItem-startDate time[datetime]')
    );
    if (startTime) return startTime;

    // 2. Fallback: время последней активности
    const latestTime = readTime(
      row.querySelector('.structItem-cell--latest time[data-time], .structItem-cell--latest time[datetime]')
    );
    if (latestTime) return latestTime;

    // 3. Fallback: любой time в строке
    return readTime(row.querySelector('time[data-time]'));
  }

  function formatDuration(ms) {
    const sec = Math.floor(ms / 1000);
    const m = Math.floor(sec / 60);
    const h = Math.floor(m / 60);
    // Только часы, без дней
    if (h > 0) return `${h}ч ${m % 60}м`;
    return `${m}м`;
  }

  function injectDeadlineBadgesInListing() {
    const config = loadDeadlineConfig();
    if (!config.enabled) return;

    const forumId = getCurrentForumId();
    if (!forumId || !FORUM_DEADLINES[forumId]) return;

    const rows = document.querySelectorAll('.structItem--thread');
    if (!rows.length) return;

    rows.forEach(row => {
      // === ВАЖНО: всегда сначала чистим классы строки и старый бейдж ===
      // (до всех return, чтобы полоса/фон не «залипали» после смены
      // префикса или состояния темы)
      row.classList.remove(
        'bbqr-deadline-row--green',
        'bbqr-deadline-row--yellow',
        'bbqr-deadline-row--red'
      );
      const oldBadge = row.querySelector('.bbqr-deadline-badge');
      if (oldBadge) oldBadge.remove();

      // === ПРОВЕРКА: есть ли префикс и нужен ли для него дедлайн? ===
            // Ищем префикс. Если его нет — считаем что ID = 0 (без префикса)
      const prefixLabel = row.querySelector('.label');
      let prefixId = 0; // По умолчанию — без префикса

      if (prefixLabel) {
        const prefixText = prefixLabel.textContent.trim();
        const foundId = getPrefixIdFromLabelText(prefixText);
        if (foundId !== null) prefixId = foundId;
      }

      // Проверяем, есть ли дедлайн для этого префикса В ЭТОМ разделе
      const sectionConfig = FORUM_DEADLINES[forumId];
      const hours = sectionConfig.prefixes[prefixId];
      if (!hours || hours <= 0) return; // Для этого префикса дедлайн не нужен — выходим

      // === ПРОВЕРКА: удалена ли тема? ===
      // Бейдж «Тема удалена» показываем ТОЛЬКО если у темы есть дедлайн-префикс,
      // иначе у тем без дедлайна (Одобрено, Отказано и т.д.) бейдж не появится.
      const isDeleted = row.querySelector('.structItem-status--deleted, i[title="Удалено"], .structItem-statuses li i.structItem-status--deleted');
      if (isDeleted) {
        const badge = document.createElement('span');
        badge.className = 'bbqr-deadline-badge';
        badge.style.cssText = 'color:#9aa5b5; font-size:12px; font-weight:600; margin-left:4px;';
        badge.textContent = '· Тема удалена';
        const prefixLink = prefixLabel.closest('a.labelLink') || prefixLabel;
        prefixLink.insertAdjacentElement('afterend', badge);
        return; // Выходим
      }

      // === Всё ок, считаем дедлайн ===
      const created = getThreadTimestampFromListing(row);
      if (!created) return;

      const deadline = created + hours * 3600000;
      const now = Date.now();
      const diff = deadline - now;
      const total = hours * 3600000;
      const leftRatio = diff / total;

      let color, text;

      if (diff <= 0) {
        color = '#ff5c5c';
        row.classList.add('bbqr-deadline-row--red');
        text = `· Просрочено ${formatDuration(Math.abs(diff))}`;
      } else if (leftRatio < 0.25) {
        color = '#ff5c5c';
        row.classList.add('bbqr-deadline-row--red');
        text = `· ${formatDuration(diff)}`;
      } else if (leftRatio < 0.5) {
        color = '#ffcc4d';
        row.classList.add('bbqr-deadline-row--yellow');
        text = `· ${formatDuration(diff)}`;
      } else {
        color = '#33c17b';
        row.classList.add('bbqr-deadline-row--green');
        text = `· ${formatDuration(diff)}`;
      }

      // Создаём бейдж
      const badge = document.createElement('span');
      badge.className = 'bbqr-deadline-badge';
      badge.style.cssText = `color:${color}; font-size:12px; font-weight:600; margin-left:4px;`;
      badge.textContent = text;

      // Вставляем ПОСЛЕ ссылки префикса
            // Вставляем бейдж: после префикса если есть, или перед названием темы
      if (prefixLabel) {
        const prefixLink = prefixLabel.closest('a.labelLink') || prefixLabel;
        prefixLink.insertAdjacentElement('afterend', badge);
      } else {
        // Нет префикса — вставляем перед ссылкой на тему
        const titleLink = row.querySelector('.structItem-title a[data-tp-primary="on"], .structItem-title a');
        if (titleLink) {
          titleLink.insertAdjacentElement('beforebegin', badge);
        }
      }
    });
  }

  function getThreadAuthorName() {
    const firstMsg = document.querySelector(
      'article.message[data-author], ' +
      '.block-container:first-child article:first-child[data-author]'
    );
    if (firstMsg) {
      const author = firstMsg.getAttribute('data-author');
      if (author) return author.trim();
    }
    // Запасной вариант: ищем ссылку с именем пользователя в первом сообщении
    const usernameLink = document.querySelector(
      'article.message:first-child .message-name a.username, ' +
      'article.message:first-child [data-xf-init] .username, ' +
      '.message:first-child a.username, ' +
      '.block-container:first-child .message:first-child .message-name a'
    );
    if (usernameLink) return usernameLink.textContent.trim();
    return '';
  }

  function replaceAnswerTags(text) {
    if (!text) return '';
    const authorName = getThreadAuthorName();
    return text
      .replace(/\{name_fa\}/gi, authorName)
      .replace(/\{name\}/gi, authorName);
  }

  function showBbqrToast(message, type = 'error') {
    let toast = document.querySelector('#bbqrToast');
    if (!toast) {
      toast = document.createElement('div');
      toast.id = 'bbqrToast';
      toast.style.cssText =
        'position:fixed; bottom:16px; right:16px; z-index:999999; max-width:340px;' +
        'border-radius:8px; padding:10px 14px; font-size:12.5px; line-height:1.4;' +
        'box-shadow:0 8px 24px rgba(0,0,0,.4);';
      document.body.appendChild(toast);
    }
    if (type === 'success') {
      toast.style.background = '#1f3a24'; toast.style.color = '#b4ffc0'; toast.style.border = '1px solid #2a7a35';
    } else {
      toast.style.background = '#3a1f22'; toast.style.color = '#ffb4bb'; toast.style.border = '1px solid #7a2a30';
    }
    toast.textContent = message;
    toast.style.display = 'block';
    clearTimeout(toast._hideTimer);
    toast._hideTimer = setTimeout(() => { toast.style.display = 'none'; }, 6000);
  }

  async function sendThreadAction(label, url, bodyObj) {
    const token = csrfToken();
    if (!token) { showBbqrToast('Быстрый ответ: не найден CSRF-токен.', 'error'); return false; }

    const attempt = (extra = {}) =>
      fetch(url, {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'X-Requested-With': 'XMLHttpRequest' },
        body: getFormDataObject({
          ...bodyObj,
          _xfToken: token,
          _xfResponseType: 'json',
          _xfWithData: 1,
          _xfRequestUri: window.location.pathname + window.location.search,
          ...extra
        }),
      });

    const readPayload = async (res) => { try { return await res.clone().json(); } catch { return null; } };
    const isSuccess = (res, payload) => res.ok && (!payload || payload.status === 'ok' || payload.status === undefined);

    try {
      let res = await attempt();
      let payload = await readPayload(res);
      console.log(`[BB Quick Reply] ${label} → HTTP ${res.status}`, payload ?? '(не JSON)');
      if (isSuccess(res, payload)) return true;

      if (payload && payload.status === 'error') {
        console.warn(`[BB Quick Reply] ${label} — ошибка, повтор с подтверждением:`, payload.errors || payload);
        res = await attempt({ _xfConfirm: 1 });
        payload = await readPayload(res);
        console.log(`[BB Quick Reply] ${label} (повтор) → HTTP ${res.status}`, payload ?? '(не JSON)');
        if (isSuccess(res, payload)) return true;
      }

      const reason = (payload && payload.errors && payload.errors.join(', ')) || `HTTP ${res.status}`;
      console.error(`[BB Quick Reply] ${label} — не выполнено:`, reason);
      showBbqrToast(`Быстрый ответ: не удалось выполнить «${label}» (${reason}).`);
      return false;
    } catch (err) {
      console.error(`[BB Quick Reply] ${label} — сетевая ошибка:`, err, url);
      showBbqrToast(`Быстрый ответ: сетевая ошибка при «${label}».`);
      return false;
    }
  }

  function clickNativeThreadLink(link) {
    return new Promise((resolve) => {
      if (!link) { resolve(false); return; }
      console.log('[BB Quick Reply] Клик по нативной ссылке:', link.getAttribute('href'), link.textContent.trim());
      try {
        link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
      } catch {
        link.click();
      }
      setTimeout(() => {
        const confirmBtn = document.querySelector(
          '.overlay.is-active .button--cta, .overlay.is-active [data-xf-click="overlay-submit"], .overlay.is-active input[type="submit"], .overlay.is-active button[type="submit"]'
        );
        if (confirmBtn) {
          console.log('[BB Quick Reply] Найден диалог подтверждения, подтверждаю:', confirmBtn);
          confirmBtn.click();
          setTimeout(() => resolve(true), 500);
        } else {
          resolve(true);
        }
      }, 500);
    });
  }

  async function runThreadToggle(label, link, fallbackUrl, fallbackBody) {
    if (link) {
      await clickNativeThreadLink(link);
      return true;
    }
    return sendThreadAction(label, fallbackUrl, { ...(fallbackBody || {}), _xfConfirm: 1 });
  }

function getThreadActionUrls_withLinks() {
    const base = getBaseThreadUrl();
    const { isSticky, isClosed, closeLink, stickLink } = getThreadFlags();
    return {
      isSticky, isClosed, closeLink, stickLink,
      editUrl: `${base}/edit`,
      closeUrl: closeLink ? closeLink.getAttribute('href') : `${base}/quick-close`,
      stickUrl: stickLink ? stickLink.getAttribute('href') : `${base}/quick-stick`,
    };
  }

  async function refreshThreadActionUrls() {
    const html = await fetch(location.href, {
      credentials: 'same-origin'
    }).then(r => r.text());

    const doc = new DOMParser().parseFromString(html, 'text/html');
    const base = getBaseThreadUrl();

    const stickLink =
      doc.querySelector('a[href*="/quick-stick"]') ||
      doc.querySelector('a[href*="/stick"]') ||
      doc.querySelector('a[href*="/unstick"]');

    const closeLink =
      doc.querySelector('a[href*="/quick-close"]') ||
      doc.querySelector('a[href*="/close"]') ||
      doc.querySelector('a[href*="/open"]');

    const isSticky = stickLink ? /Открепить/i.test(stickLink.textContent) : false;
    const isClosed = closeLink ? /Открыть/i.test(closeLink.textContent) : false;

    return {
      isSticky,
      isClosed,
      closeLink,
      stickLink,
      editUrl: `${base}/edit`,
      closeUrl: closeLink ? closeLink.getAttribute('href') : `${base}/quick-close`,
      stickUrl: stickLink ? stickLink.getAttribute('href') : `${base}/quick-stick`,
    };
  }

async function applyThreadSettingsFromQuickAnswer(settings) {
    if (!settings) return false;
    if (!getThreadIdFromUrl()) return false;

    const {
      prefix_id, threadStick, threadCloser,
      _editUrl, _closeUrl, _stickUrl,
      _isSticky, _isClosed, _threadTitle
    } = settings;

    const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

    const wantPrefix = prefix_id !== undefined && prefix_id !== null && String(prefix_id) !== '-1';
    const wantSticky = threadStick === '1';
    const wantUnsticky = threadStick === '2';
    const wantClosed = threadCloser === '1';
    const wantOpened = threadCloser === '2';

    let state = getThreadActionUrls_withLinks();

    if (wantPrefix) {
      const editUrl = _editUrl || `${getBaseThreadUrl()}/edit`;
      const threadTitle = _threadTitle || getThreadTitle();
      if (!threadTitle) {
        showBbqrToast('Не удалось определить заголовок темы.', 'error');
        return false;
      }
      const ok = await sendThreadAction('изменение префикса', editUrl, {
        prefix_id: String(prefix_id),
        title: threadTitle,
      });
      if (!ok) {
        showBbqrToast('Не удалось изменить префикс. Дальнейшие действия отменены.', 'error');
        return false;
      }
      await sleep(300);
      state = await refreshThreadActionUrls();
    }

    if (threadStick === '1' || threadStick === '2') {
      const needStick = wantSticky && !state.isSticky;
      const needUnstick = wantUnsticky && state.isSticky;

      if (needStick || needUnstick) {
        await sleep(300);
        const label = needStick ? 'закрепление темы' : 'открепление темы';
        const ok = await runThreadToggle(label, null, state.stickUrl, {});
        if (!ok) {
          showBbqrToast(`Не удалось выполнить «${label}». Дальнейшие действия отменены.`, 'error');
          return false;
        }
        state.isSticky = !state.isSticky;
      }
    }

    if (threadCloser === '1' || threadCloser === '2') {
      const needClose = wantClosed && !state.isClosed;
      const needOpen = wantOpened && state.isClosed;

      if (needClose || needOpen) {
        await sleep(300);
        const label = needClose ? 'закрытие темы' : 'открытие темы';
        const ok = await runThreadToggle(label, null, state.closeUrl, {});
        if (!ok) {
          showBbqrToast(`Не удалось выполнить «${label}».`, 'error');
          return false;
        }
        state.isClosed = !state.isClosed;
      }
    }

    return true;
}

  async function applyPrefixNow(prefixId) {
    const { editUrl } = getThreadActionUrls();
    const threadTitle = getThreadTitle();
    if (!threadTitle) { showBbqrToast('Не удалось определить заголовок темы.', 'error'); return; }
    const ok = await sendThreadAction('изменение префикса', editUrl, { prefix_id: String(prefixId), title: threadTitle });
    if (ok) { showBbqrToast('Готово: префикс изменён.', 'success'); setTimeout(() => window.location.reload(), 600); }
  }

  async function toggleCloseNow() {
    const { isClosed, closeLink, closeUrl } = getThreadActionUrls_withLinks();
    const ok = await runThreadToggle(isClosed ? 'открытие темы' : 'закрытие темы', closeLink, closeUrl, {});
    if (ok) { showBbqrToast('Готово: состояние темы изменено.', 'success'); setTimeout(() => window.location.reload(), 600); }
  }

  async function toggleStickNow() {
    const { isSticky, stickLink, stickUrl } = getThreadActionUrls_withLinks();
    const ok = await runThreadToggle(isSticky ? 'открепление темы' : 'закрепление темы', stickLink, stickUrl, {});
    if (ok) { showBbqrToast('Готово: состояние темы изменено.', 'success'); setTimeout(() => window.location.reload(), 600); }
  }

  let bbqrIsApplying = false;

function pickAnswer(a) {
    const { isSticky, isClosed, closeLink, stickLink } = getThreadFlags();
    const { editUrl, closeUrl, stickUrl } = getThreadActionUrls_withLinks();
    const threadTitle = getThreadTitle();
    const token = csrfToken();

    bbLastQuickSettings = {
      prefix_id: a.prefix_id ?? -1,
      threadStick: a.threadStick ?? 'no',
      threadCloser: a.threadCloser ?? 'no',
      _editUrl: editUrl,
      _closeUrl: closeUrl,
      _stickUrl: stickUrl,
      _isSticky: isSticky,
      _isClosed: isClosed,
      _threadTitle: threadTitle,
      _csrfToken: token,
    };
    setEditorMessageText(replaceAnswerTags(a.content) || '');

    const list = loadQuickAnswers();
    const idx = list.findIndex((x) => x.id === a.id);
    if (idx !== -1) {
      list[idx].usageCount = (list[idx].usageCount || 0) + 1;
      list[idx].lastUsedAt = Date.now();
      saveQuickAnswers(list);
    }

    let recent = loadRecentAnswers().filter((id) => id !== a.id);
    recent.unshift(a.id);
    if (recent.length > 20) recent.length = 20;
    saveRecentAnswers(recent);
  }

  function injectBbqrGlobalStyles() {
    if (document.querySelector('#bbqrGlobalStyles')) return;
    const style = document.createElement('style');
    style.id = 'bbqrGlobalStyles';
    style.textContent = `
      .bbqr-themed { display:flex; flex-direction:column; overflow:hidden; border-radius:12px;
        background:var(--bbqr-bg); color:var(--bbqr-text); border:1px solid var(--bbqr-border);
        box-shadow:0 20px 60px rgba(0,0,0,.6); max-height:90vh; }
      .bbqr-shell-header { padding:16px 22px; background:var(--bbqr-card); border-bottom:1px solid var(--bbqr-border);
        display:flex; align-items:center; gap:12px; flex-wrap:wrap; flex:0 0 auto; }
      .bbqr-shell-title { font-size:17px; font-weight:700; }
      .bbqr-shell-header-actions { margin-left:auto; display:flex; align-items:center; gap:8px; }
      .bbqr-shell-close { cursor:pointer; width:26px; height:26px; display:flex;
        align-items:center; justify-content:center; border-radius:7px; color:var(--bbqr-text-dim); }
      .bbqr-shell-close:hover { background:rgba(120,120,140,.2); color:var(--bbqr-text); }
      .bbqr-shell-close::after { content:"\\2715"; font-size:13px; }
      .bbqr-shell-settings { cursor:pointer; width:26px; height:26px; display:flex;
        align-items:center; justify-content:center; border-radius:7px; color:var(--bbqr-text-dim); font-size:15px; }
      .bbqr-shell-settings:hover { background:rgba(120,120,140,.2); color:var(--bbqr-text); }
      .bbqr-shell-body { padding:20px; max-height:78vh; overflow-y:auto; background:var(--bbqr-bg);
        flex:1 1 auto; min-height:0; -webkit-overflow-scrolling:touch; overscroll-behavior:contain; touch-action:pan-y; }
      .bbqr-shell-footer { padding:14px 22px; background:var(--bbqr-card); border-top:1px solid var(--bbqr-border);
        display:flex; justify-content:flex-end; gap:10px; flex-wrap:wrap; flex:0 0 auto; }

      @media (max-width: 640px) {
        .bbqr-themed { max-height:92vh; border-radius:10px; }
        .bbqr-shell-header { padding:12px 14px; }
        .bbqr-shell-body { padding:14px; max-height:none; }
        .bbqr-shell-footer { padding:12px 14px; }
        .bbqr-grid { grid-template-columns:repeat(auto-fill, minmax(140px, 1fr)) !important; }
      }

      .bbqr-search { width:100%; box-sizing:border-box; padding:10px 14px; border-radius:10px;
        border:1px solid var(--bbqr-border); background:var(--bbqr-card); color:var(--bbqr-text);
        font-size:13.5px; margin-bottom:14px; }
      .bbqr-search:focus { outline:none; border-color:var(--bbqr-accent); }

      .bbqr-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(190px, 1fr)); gap:10px; }

      .bbqr-card { position:relative; background:var(--bbqr-card); border:1px solid var(--bbqr-border);
        border-radius:12px; padding:14px 34px 12px 14px; cursor:pointer; min-height:60px;
        display:flex; flex-direction:column; justify-content:space-between;
        transition:border-color .15s ease, transform .1s ease; }
      .bbqr-card:hover { border-color:var(--bbqr-accent); transform:translateY(-2px); }
      .bbqr-card__title { font-weight:600; font-size:13.5px; word-break:break-word; }
      .bbqr-card__meta { font-size:11px; color:var(--bbqr-text-dim); margin-top:6px; }
      .bbqr-card__pin { position:absolute; top:10px; right:10px; font-size:15px; opacity:.35; cursor:pointer; }
      .bbqr-card__pin.is-pinned { opacity:1; color:#ffcc4d; }
      .bbqr-card__actions { display:flex; gap:8px; margin-top:10px; }
      .bbqr-card__actions span { font-size:13px; opacity:.6; cursor:pointer; width:24px; height:24px;
        display:flex; align-items:center; justify-content:center; border-radius:6px; }
      .bbqr-card__actions span:hover { opacity:1; background:rgba(120,120,140,.2); }

      .bbqr-empty { color:var(--bbqr-text-dim); font-size:13px; padding:16px 2px; text-align:center; }

      .bbqr-btn { appearance:none; border:1px solid var(--bbqr-border); background:var(--bbqr-card);
        color:var(--bbqr-text); border-radius:9px; padding:8px 16px; font-size:13px; font-weight:600;
        cursor:pointer; transition:filter .15s ease, transform .1s ease; }
      .bbqr-btn:hover { filter:brightness(1.15); transform:translateY(-1px); }
      .bbqr-btn:disabled { opacity:.4; cursor:not-allowed; transform:none; filter:none; }
      .bbqr-btn--primary { background:var(--bbqr-accent); border-color:var(--bbqr-accent); color:#fff; }

      .bbqr-field { margin-bottom:14px; }
      .bbqr-label { display:block; font-size:12.5px; font-weight:700; margin-bottom:6px; }
      .bbqr-hint { font-size:11.5px; color:var(--bbqr-text-dim); margin-top:6px; }
      .bbqr-input, .bbqr-textarea, .bbqr-select { width:100%; box-sizing:border-box; background:var(--bbqr-card);
        border:1px solid var(--bbqr-border); color:var(--bbqr-text); border-radius:9px; padding:9px 11px;
        font-size:13px; outline:none; }
      .bbqr-input:focus, .bbqr-textarea:focus, .bbqr-select:focus { border-color:var(--bbqr-accent); }
      .bbqr-textarea { resize:vertical; min-height:90px; }

      .bbqr-radio-group { display:flex; flex-wrap:wrap; gap:8px; }
      .bbqr-radio { position:relative; }
      .bbqr-radio input { position:absolute; opacity:0; }
      .bbqr-radio span { display:inline-block; padding:6px 12px; font-size:12.5px; border-radius:999px;
        border:1px solid var(--bbqr-border); background:var(--bbqr-card); color:var(--bbqr-text-dim); cursor:pointer; }
      .bbqr-radio input:checked + span { background:var(--bbqr-accent); border-color:var(--bbqr-accent); color:#fff; }

      .bbqr-theme-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(120px,1fr)); gap:10px; }
      .bbqr-theme-swatch { border-radius:10px; padding:16px 10px; text-align:center; font-size:12.5px;
        font-weight:700; cursor:pointer; border:2px solid transparent; }
      .bbqr-theme-swatch.is-active { box-shadow:0 0 0 2px var(--bbqr-accent) inset; }

      .bbqr-page-chip { padding:8px 14px; font-size:12.5px; border-radius:999px; cursor:pointer;
        border:1px solid rgba(120,120,140,.3); background:rgba(120,120,140,.08); font-weight:600;
        color:#ffffff; transition:transform .1s ease, border-color .15s ease, background .15s ease, color .15s ease; }
      .bbqr-page-chip:hover { transform:translateY(-1px); border-color:rgba(90,140,255,.6); background:rgba(90,140,255,.1); color:#ffffff; }
      .bbqr-page-chip--icon { padding:8px 12px; }
      .bbqr-deadline-badge { white-space:nowrap; }

      /* === ДЕДЛАЙН: визуальные эффекты строк в списке тем === */
      .structItem--thread.bbqr-deadline-row--green { box-shadow: inset 3px 0 0 #33c17b; }
      .structItem--thread.bbqr-deadline-row--yellow {
        box-shadow: inset 3px 0 0 #ffcc4d;
        background: rgba(255,204,77,.07);
      }
      .structItem--thread.bbqr-deadline-row--red {
        box-shadow: inset 3px 0 0 #ff5c5c;
        animation: bbqrDeadlinePulse 2s ease-in-out infinite;
      }
      @keyframes bbqrDeadlinePulse {
        0%, 100% { background: rgba(255,92,92,.08); }
        50%      { background: rgba(255,92,92,.16); }
      }
    `;
    document.head.appendChild(style);
  }

  function overlayShell({ title, bodyHtml, footerHtml = '', maxWidth = 900, headerExtraHtml = '' }) {
    const t = themeColors();
    const vars = `--bbqr-bg:${t.bg}; --bbqr-card:${t.card}; --bbqr-border:${t.border}; --bbqr-text:${t.text}; --bbqr-text-dim:${t.textDim}; --bbqr-accent:${t.accent};`;
    return `
      <div class="overlay bbqr-themed" style="${vars} max-width:${maxWidth}px;">
        <div class="bbqr-shell-header">
          <span class="bbqr-shell-title">${title}</span>
          <div class="bbqr-shell-header-actions">
            ${headerExtraHtml}
            <a class="bbqr-shell-close" data-bbqr-shell-close aria-label="Закрыть"></a>
          </div>
        </div>
        <div class="bbqr-shell-body">${bodyHtml}</div>
        ${footerHtml ? `<div class="bbqr-shell-footer">${footerHtml}</div>` : ''}
      </div>`;
  }

  function openOverlay(id, innerHtml) {
    document.querySelector(`#${id}`)?.remove();
    const overlay = document.createElement('div');
    overlay.id = id;
    overlay.className = 'overlay-container is-active';
    overlay.style.zIndex = '999999';
    overlay.innerHTML = innerHtml;
    document.body.appendChild(overlay);
    trackOverlayOpen();

    const close = () => { overlay.remove(); trackOverlayClose(); };
    overlay.querySelector('[data-bbqr-shell-close]')?.addEventListener('click', close);
    overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
    return { overlay, close };
  }

  function exportSettings() {
    const data = {
      answers: loadQuickAnswers(),
      folders: loadQuickFolders(),
      recent: loadRecentAnswers(),
      prefixes: [...loadHomePrefixes()],
      actions: [...loadHomeActions()],
      theme: loadTheme(),
      version: '2.0',
      exportedAt: new Date().toISOString(),
    };
    const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `bb_quick_reply_settings_${Date.now()}.json`;
    a.click();
    URL.revokeObjectURL(url);
    showBbqrToast('Настройки экспортированы.', 'success');
  }

  function importSettings(jsonString) {
    let data;
    try { data = JSON.parse(jsonString); }
    catch (err) {
      console.error('[BB Quick Reply] Импорт: некорректный JSON.', err);
      showBbqrToast('Не удалось прочитать файл: это не корректный JSON.', 'error');
      return false;
    }
    if (data.answers) saveQuickAnswers(data.answers);
    if (data.folders) saveQuickFolders(data.folders);
    if (data.recent) saveRecentAnswers(data.recent);
    if (data.prefixes) saveHomePrefixes(new Set(data.prefixes));
    if (data.actions) saveHomeActions(new Set(data.actions));
    if (data.theme && THEMES[data.theme]) saveTheme(data.theme);
    migrateData();
    showBbqrToast('Настройки импортированы!', 'success');
    return true;
  }

  function triggerImportFilePicker(onDone) {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'application/json,.json';
    input.style.display = 'none';
    document.body.appendChild(input);
    input.addEventListener('change', () => {
      const file = input.files && input.files[0];
      if (!file) { input.remove(); return; }
      const reader = new FileReader();
      reader.onload = () => {
        const ok = importSettings(String(reader.result || ''));
        input.remove();
        if (ok && typeof onDone === 'function') onDone();
      };
      reader.onerror = () => { showBbqrToast('Не удалось прочитать файл.', 'error'); input.remove(); };
      reader.readAsText(file);
    });
    input.click();
  }

  function ensureInlineAnswerButtons() {
    const replyBtn = document.querySelector(REPLY_BTN_SELECTOR);
    if (!replyBtn) return;
    const anchor = replyBtn.closest('.formButtonGroup') || replyBtn.parentElement;
    if (!anchor) return;

    const homePrefixes = loadHomePrefixes();
    const homeActions = loadHomeActions();
    const { isClosed, isSticky } = getThreadFlags();

    let prefixRow = document.querySelector('#bbqrPrefixInlineRow');
    if (!prefixRow) {
      prefixRow = document.createElement('div');
      prefixRow.id = 'bbqrPrefixInlineRow';
      prefixRow.style.cssText = 'display:flex; flex-wrap:wrap; gap:6px; margin-bottom:8px;';
      anchor.insertAdjacentElement('beforebegin', prefixRow);
    }

    const sig1 = [...homePrefixes].sort().join('|') + '::' + [...homeActions].sort().join('|') + `::${isClosed}:${isSticky}`;
    if (prefixRow.dataset.sig !== sig1) {
      prefixRow.dataset.sig = sig1;
      prefixRow.innerHTML = '';

      homePrefixes.forEach((label) => {
        const p = PREFIX_MAP.find((x) => x[0] === label);
        if (!p) return;
        const b = document.createElement('button');
        b.type = 'button';
        b.className = 'bbqr-page-chip';
        b.textContent = label;
        b.addEventListener('click', () => applyPrefixNow(p[1]));
        prefixRow.appendChild(b);
      });

      if (homeActions.has('close')) {
        const b = document.createElement('button');
        b.type = 'button';
        b.className = 'bbqr-page-chip';
        b.textContent = isClosed ? 'Открыть тему' : 'Закрыть тему';
        b.addEventListener('click', () => toggleCloseNow());
        prefixRow.appendChild(b);
      }
      if (homeActions.has('stick')) {
        const b = document.createElement('button');
        b.type = 'button';
        b.className = 'bbqr-page-chip';
        b.textContent = isSticky ? 'Открепить тему' : 'Закрепить тему';
        b.addEventListener('click', () => toggleStickNow());
        prefixRow.appendChild(b);
      }

      prefixRow.style.display = prefixRow.children.length ? '' : 'none';
    }

    const allAnswers = loadQuickAnswers();
    const recentIds = loadRecentAnswers();
    const recentAnswers = recentIds
      .map((id) => allAnswers.find((a) => a.id === id))
      .filter(Boolean)
      .slice(0, MAX_RECENT_SHOWN);

    let row = document.querySelector('#bbqrInlineRow');
    if (!row) {
      row = document.createElement('div');
      row.id = 'bbqrInlineRow';
      row.style.cssText = 'display:flex; flex-wrap:wrap; gap:6px; margin-top:8px; align-items:center;';
      anchor.insertAdjacentElement('afterend', row);
    }

    const sig2 = recentAnswers.map((a) => a.id + ':' + a.title).join('|');
    if (row.dataset.sig === sig2) return;
    row.dataset.sig = sig2;
    row.innerHTML = '';

    recentAnswers.forEach((a) => {
      const b = document.createElement('button');
      b.type = 'button';
      b.className = 'bbqr-page-chip';
      b.textContent = a.title || 'Ответ';
      b.addEventListener('click', () => pickAnswer(a));
      row.appendChild(b);
    });

    const mainBtn = document.createElement('button');
    mainBtn.type = 'button';
    mainBtn.className = 'bbqr-page-chip';
    mainBtn.style.opacity = '.9';
    mainBtn.textContent = 'Быстрый ответ';
    mainBtn.addEventListener('click', () => renderQuickAnswersOverlay());
    row.appendChild(mainBtn);

    const homeBtn = document.createElement('button');
    homeBtn.type = 'button';
    homeBtn.className = 'bbqr-page-chip';
    homeBtn.style.opacity = '.85';
    homeBtn.textContent = 'Кнопки на главный экран';
    homeBtn.addEventListener('click', () => renderPrefixControlOverlay());
    row.appendChild(homeBtn);
  }

  function renderAnswerCard(a) {
    return `
      <div class="bbqr-card" data-bbqr-pick="${a.id}">
        <span class="bbqr-card__pin ${a.pinned ? 'is-pinned' : ''}" data-bbqr-pin-answer="${a.id}" title="Закрепить">★</span>
        <div class="bbqr-card__title">${escapeHtml(a.title || 'Ответ')}</div>
        <div class="bbqr-card__meta">Использован: ${a.usageCount || 0}</div>
        <div class="bbqr-card__actions">
          <span data-bbqr-edit="${a.id}" title="Редактировать">✎</span>
          <span data-bbqr-del="${a.id}" title="Удалить">✕</span>
        </div>
      </div>`;
  }

  function renderAnswerGrid(list) {
    if (!list.length) return '<div class="bbqr-empty">Ничего не найдено.</div>';
    return `<div class="bbqr-grid">${list.map(renderAnswerCard).join('')}</div>`;
  }

  function renderFolderCard(f, count) {
    return `
      <div class="bbqr-card" data-bbqr-open-folder="${f.id}">
        <span class="bbqr-card__pin ${f.pinned ? 'is-pinned' : ''}" data-bbqr-pin-folder="${f.id}" title="Закрепить">★</span>
        <div class="bbqr-card__title">📁 ${escapeHtml(f.title || 'Папка')}</div>
        <div class="bbqr-card__meta">${count} ${count === 1 ? 'ответ' : 'ответов'}</div>
        <div class="bbqr-card__actions">
          <span data-bbqr-edit-folder="${f.id}" title="Переименовать">✎</span>
          <span data-bbqr-del-folder="${f.id}" title="Удалить папку">✕</span>
        </div>
      </div>`;
  }

  function renderFolderGrid(folders, allAnswers) {
    if (!folders.length) return '<div class="bbqr-empty">Пока нет папок. Создайте первую, чтобы добавить ответы.</div>';
    return `<div class="bbqr-grid">${folders
      .map((f) => renderFolderCard(f, allAnswers.filter((a) => a.folderId === f.id).length))
      .join('')}</div>`;
  }

  function buildQuickAnswersResultsHtml(folderId, search) {
    const allAnswers = loadQuickAnswers();
    const folders = sortByPinAndUsage(loadQuickFolders());
    const searchTrim = search.trim().toLowerCase();

    if (searchTrim) {
      const matches = allAnswers.filter(
        (a) => (a.title || '').toLowerCase().includes(searchTrim) || (a.content || '').toLowerCase().includes(searchTrim)
      );
      return renderAnswerGrid(sortByPinAndUsage(matches));
    }
    if (folderId) {
      const folder = folders.find((f) => f.id === folderId);
      const inFolder = sortByPinAndUsage(allAnswers.filter((a) => a.folderId === folderId));
      return `
        <div style="display:flex; align-items:center; gap:10px; margin-bottom:14px; flex-wrap:wrap;">
          <button type="button" class="bbqr-btn" data-bbqr-back>← Папки</button>
          <div style="font-weight:700; font-size:15px;">📁 ${escapeHtml(folder?.title || 'Папка')}</div>
          <button type="button" class="bbqr-btn bbqr-btn--primary" style="margin-left:auto;" data-bbqr-create-in-folder="${folderId}">+ Ответ</button>
        </div>
        ${renderAnswerGrid(inFolder)}`;
    }
    return `
      <div style="display:flex; justify-content:flex-end; margin-bottom:14px;">
        <button type="button" class="bbqr-btn bbqr-btn--primary" data-bbqr-create-folder>+ Папка</button>
      </div>
      ${renderFolderGrid(folders, allAnswers)}`;
  }

  function renderQuickAnswersOverlay(state = {}) {
    const folderId = state.folderId ?? null;
    const initialSearch = state.search ?? '';

    const bodyHtml =
      `<input type="text" class="bbqr-search" id="bbqrSearchInput" placeholder="Поиск по названию и тексту ответа..." value="${escapeHtml(initialSearch)}" />` +
      `<div id="bbqrResultsArea">${buildQuickAnswersResultsHtml(folderId, initialSearch)}</div>`;

    const html = overlayShell({
      title: 'Быстрый ответ',
      bodyHtml,
      maxWidth: 920,
      headerExtraHtml: '<a class="bbqr-shell-settings" data-bbqr-open-settings title="Настройки">⚙</a>',
    });
    const { overlay, close } = openOverlay('bbqrAnswersOverlay', html);

    const liveState = { folderId, search: initialSearch };

    overlay.querySelector('[data-bbqr-open-settings]')?.addEventListener('click', () => {
      close();
      renderSettingsOverlay();
    });

    const searchInput = overlay.querySelector('#bbqrSearchInput');
    searchInput?.addEventListener('input', () => {
      liveState.search = searchInput.value;
      const resultsArea = overlay.querySelector('#bbqrResultsArea');
      if (resultsArea) resultsArea.innerHTML = buildQuickAnswersResultsHtml(liveState.folderId, liveState.search);
    });

    overlay.addEventListener('click', (e) => {
      if (e.target.closest('[data-bbqr-back]')) { close(); renderQuickAnswersOverlay({}); return; }

      if (e.target.closest('[data-bbqr-create-folder]')) { close(); renderCreateFolderOverlay(); return; }

      const createInFolder = e.target.closest('[data-bbqr-create-in-folder]');
      if (createInFolder) {
        close();
        renderCreateAnswerOverlay(null, null, createInFolder.getAttribute('data-bbqr-create-in-folder'));
        return;
      }

      const openFolder = e.target.closest('[data-bbqr-open-folder]');
      if (openFolder && !e.target.closest('[data-bbqr-pin-folder], [data-bbqr-edit-folder], [data-bbqr-del-folder]')) {
        close();
        renderQuickAnswersOverlay({ folderId: openFolder.getAttribute('data-bbqr-open-folder') });
        return;
      }

      const pinFolder = e.target.closest('[data-bbqr-pin-folder]');
      if (pinFolder) {
        e.stopPropagation();
        const id = pinFolder.getAttribute('data-bbqr-pin-folder');
        const list = loadQuickFolders();
        const f = list.find((x) => x.id === id);
        if (f) { f.pinned = !f.pinned; saveQuickFolders(list); }
        close();
        renderQuickAnswersOverlay({ folderId: liveState.folderId, search: liveState.search });
        return;
      }

      const editFolder = e.target.closest('[data-bbqr-edit-folder]');
      if (editFolder) {
        e.stopPropagation();
        const id = editFolder.getAttribute('data-bbqr-edit-folder');
        const f = loadQuickFolders().find((x) => x.id === id);
        close();
        renderCreateFolderOverlay(f);
        return;
      }

      const delFolder = e.target.closest('[data-bbqr-del-folder]');
      if (delFolder) {
        e.stopPropagation();
        const id = delFolder.getAttribute('data-bbqr-del-folder');
        const folderTitle = loadQuickFolders().find((x) => x.id === id)?.title || 'папку';
        if (!window.confirm(`Удалить папку «${folderTitle}» вместе со всеми ответами внутри неё?`)) return;
        saveQuickFolders(loadQuickFolders().filter((x) => x.id !== id));
        saveQuickAnswers(loadQuickAnswers().filter((a) => a.folderId !== id));
        saveRecentAnswers(loadRecentAnswers().filter((rid) => {
          const a = loadQuickAnswers().find((x) => x.id === rid);
          return !!a;
        }));
        close();
        renderQuickAnswersOverlay({});
        return;
      }

      const delBtn = e.target.closest('[data-bbqr-del]');
      if (delBtn) {
        e.stopPropagation();
        const id = delBtn.getAttribute('data-bbqr-del');
        saveQuickAnswers(loadQuickAnswers().filter((a) => a.id !== id));
        saveRecentAnswers(loadRecentAnswers().filter((rid) => rid !== id));
        close();
        renderQuickAnswersOverlay({ folderId: liveState.folderId, search: liveState.search });
        return;
      }

      const editBtn = e.target.closest('[data-bbqr-edit]');
      if (editBtn) {
        e.stopPropagation();
        const id = editBtn.getAttribute('data-bbqr-edit');
        const a = loadQuickAnswers().find((x) => x.id === id);
        close();
        renderCreateAnswerOverlay(a, id, a?.folderId);
        return;
      }

      const pinAnswer = e.target.closest('[data-bbqr-pin-answer]');
      if (pinAnswer) {
        e.stopPropagation();
        const id = pinAnswer.getAttribute('data-bbqr-pin-answer');
        const list = loadQuickAnswers();
        const a = list.find((x) => x.id === id);
        if (a) { a.pinned = !a.pinned; saveQuickAnswers(list); }
        close();
        renderQuickAnswersOverlay({ folderId: liveState.folderId, search: liveState.search });
        return;
      }

      const pick = e.target.closest('[data-bbqr-pick]');
      if (pick) {
        const id = pick.getAttribute('data-bbqr-pick');
        const a = loadQuickAnswers().find((x) => x.id === id);
        if (a) { pickAnswer(a); close(); }
      }
    });
  }

  function renderCreateFolderOverlay(existing = null) {
    const bodyHtml = `
      <div class="bbqr-field">
        <label class="bbqr-label">Название папки</label>
        <input type="text" class="bbqr-input" id="bbqrFolderTitle" maxlength="60" value="${escapeHtml(existing?.title || '')}" />
      </div>`;
    const footerHtml = `
      <button type="button" class="bbqr-btn" data-bbqr-cancel>Отмена</button>
      <button type="button" class="bbqr-btn bbqr-btn--primary" id="bbqrSaveFolder">${existing ? 'Сохранить' : 'Создать папку'}</button>`;

    const html = overlayShell({ title: existing ? 'Переименовать папку' : 'Новая папка', bodyHtml, footerHtml, maxWidth: 480 });
    const { overlay, close } = openOverlay('bbqrFolderOverlay', html);

    const backToList = () => { close(); renderQuickAnswersOverlay({}); };
    overlay.querySelector('[data-bbqr-shell-close]')?.addEventListener('click', backToList);
    overlay.querySelector('[data-bbqr-cancel]')?.addEventListener('click', backToList);

    overlay.querySelector('#bbqrSaveFolder')?.addEventListener('click', () => {
      const titleInput = overlay.querySelector('#bbqrFolderTitle');
      const title = titleInput.value.trim();
      if (!title) { titleInput.focus(); return; }

      const list = loadQuickFolders();
      if (existing) {
        const f = list.find((x) => x.id === existing.id);
        if (f) f.title = title;
      } else {
        list.push({ id: genId(), title, pinned: false, usageCount: 0, createdAt: Date.now() });
      }
      saveQuickFolders(list);
      close();
      renderQuickAnswersOverlay({});
    });
  }

  function renderCreateAnswerOverlay(existing = null, editId = null, forcedFolderId = null) {
    const folders = loadQuickFolders();
    const stick = existing?.threadStick ?? 'no';
    const closer = existing?.threadCloser ?? 'no';
    const prefixId = existing?.prefix_id ?? -1;
    const selectedFolderId = existing?.folderId ?? forcedFolderId ?? (folders[0]?.id ?? '');

    if (!folders.length) {
      const bodyHtml = `<div class="bbqr-empty">Сначала нужно создать папку — ответы обязательно должны лежать в папке.</div>`;
      const footerHtml = `
        <button type="button" class="bbqr-btn" data-bbqr-cancel>Закрыть</button>
        <button type="button" class="bbqr-btn bbqr-btn--primary" id="bbqrGoCreateFolder">+ Создать папку</button>`;
      const html = overlayShell({ title: 'Создание ответа', bodyHtml, footerHtml, maxWidth: 480 });
      const { overlay, close } = openOverlay('bbqrCreateOverlay', html);
      const backToList = () => { close(); renderQuickAnswersOverlay({}); };
      overlay.querySelector('[data-bbqr-shell-close]')?.addEventListener('click', backToList);
      overlay.querySelector('[data-bbqr-cancel]')?.addEventListener('click', backToList);
      overlay.querySelector('#bbqrGoCreateFolder')?.addEventListener('click', () => { close(); renderCreateFolderOverlay(); });
      return;
    }

    const folderOptionsHtml = folders.map((f) => `<option value="${f.id}">${escapeHtml(f.title)}</option>`).join('');
    const prefixOptionsHtml =
      '<option value="-1">(Не изменять)</option><option value="0">(Без префикса)</option>' +
      PREFIX_MAP.map(([label, id]) => `<option value="${id}">${escapeHtml(label)}</option>`).join('');

    const bodyHtml = `
      <div class="bbqr-field">
        <label class="bbqr-label">Папка<span style="color:var(--bbqr-accent);">*</span></label>
        <select class="bbqr-select" id="bbqrFolderSelect">${folderOptionsHtml}</select>
      </div>

      <div class="bbqr-field">
        <label class="bbqr-label">Название кнопки<span style="color:var(--bbqr-accent);">*</span></label>
        <input type="text" class="bbqr-input" id="bbqrTitleButton" maxlength="40" value="${escapeHtml(existing?.title || '')}" />
      </div>

      <div class="bbqr-field">
        <label class="bbqr-label">Текст<span style="color:var(--bbqr-accent);">*</span></label>
        <textarea class="bbqr-textarea" id="bbqrAnswerText">${escapeHtml(existing?.content || '')}</textarea>
      </div>

      <div class="bbqr-field">
        <label class="bbqr-label">Закрыть тему</label>
        <div class="bbqr-radio-group">
          <label class="bbqr-radio"><input type="radio" name="bbqrThreadCloser" value="no" ${closer === 'no' ? 'checked' : ''}><span>Не изменять</span></label>
          <label class="bbqr-radio"><input type="radio" name="bbqrThreadCloser" value="1" ${closer === '1' ? 'checked' : ''}><span>Закрыть</span></label>
          <label class="bbqr-radio"><input type="radio" name="bbqrThreadCloser" value="2" ${closer === '2' ? 'checked' : ''}><span>Открыть</span></label>
        </div>
      </div>

      <div class="bbqr-field">
        <label class="bbqr-label">Закрепить тему</label>
        <div class="bbqr-radio-group">
          <label class="bbqr-radio"><input type="radio" name="bbqrThreadStick" value="no" ${stick === 'no' ? 'checked' : ''}><span>Не изменять</span></label>
          <label class="bbqr-radio"><input type="radio" name="bbqrThreadStick" value="1" ${stick === '1' ? 'checked' : ''}><span>Закрепить</span></label>
          <label class="bbqr-radio"><input type="radio" name="bbqrThreadStick" value="2" ${stick === '2' ? 'checked' : ''}><span>Открепить</span></label>
        </div>
      </div>

      <div class="bbqr-field">
        <label class="bbqr-label">Изменение префикса</label>
        <select class="bbqr-select" id="bbqrPrefixId">${prefixOptionsHtml}</select>
      </div>`;

    const footerHtml = `
      <button type="button" class="bbqr-btn" data-bbqr-cancel>Отмена</button>
      <button type="button" class="bbqr-btn bbqr-btn--primary" id="bbqrSaveAnswer">${existing ? 'Сохранить' : 'Добавить ответ'}</button>`;

    const html = overlayShell({ title: existing ? 'Редактирование ответа' : 'Создание ответа', bodyHtml, footerHtml, maxWidth: 640 });
    const { overlay, close } = openOverlay('bbqrCreateOverlay', html);

    const folderSelect = overlay.querySelector('#bbqrFolderSelect');
    if (folderSelect) folderSelect.value = String(selectedFolderId);
    const prefixSelect = overlay.querySelector('#bbqrPrefixId');
    if (prefixSelect) prefixSelect.value = String(prefixId);

    const backToList = () => { close(); renderQuickAnswersOverlay({ folderId: existing?.folderId ?? forcedFolderId }); };
    overlay.querySelector('[data-bbqr-shell-close]')?.addEventListener('click', backToList);
    overlay.querySelector('[data-bbqr-cancel]')?.addEventListener('click', backToList);

    overlay.querySelector('#bbqrSaveAnswer')?.addEventListener('click', () => {
      const title = overlay.querySelector('#bbqrTitleButton')?.value.trim() || 'Мой ответ';
      const content = overlay.querySelector('#bbqrAnswerText')?.value || '';
      if (!content.trim()) { overlay.querySelector('#bbqrAnswerText')?.focus(); return; }

      const folderId = overlay.querySelector('#bbqrFolderSelect')?.value;
      const stickVal = overlay.querySelector('input[name="bbqrThreadStick"]:checked')?.value || 'no';
      const closerVal = overlay.querySelector('input[name="bbqrThreadCloser"]:checked')?.value || 'no';
      const prefixVal = overlay.querySelector('#bbqrPrefixId')?.value;
      const prefix_id = prefixVal === undefined ? -1 : Number(prefixVal);

      const list = loadQuickAnswers();
      if (editId) {
        const idx = list.findIndex((x) => x.id === editId);
        if (idx !== -1) {
          list[idx] = {
            ...list[idx],
            title, content, folderId,
            threadStick: String(stickVal),
            threadCloser: String(closerVal),
            prefix_id: Number.isNaN(prefix_id) ? -1 : prefix_id,
            updatedAt: Date.now(),
          };
        }
      } else {
        list.push({
          id: genId(), title, content, folderId,
          threadStick: String(stickVal),
          threadCloser: String(closerVal),
          prefix_id: Number.isNaN(prefix_id) ? -1 : prefix_id,
          pinned: false, usageCount: 0,
          createdAt: Date.now(), updatedAt: Date.now(),
        });
      }
      saveQuickAnswers(list);
      close();
      renderQuickAnswersOverlay({ folderId });
    });
  }

  function renderSettingsOverlay() {
    const currentTheme = loadTheme();

    const themeSwatchesHtml = Object.entries(THEMES)
      .map(([key, th]) => `
        <div class="bbqr-theme-swatch ${key === currentTheme ? 'is-active' : ''}"
             data-bbqr-theme="${key}"
             style="background:${th.bg}; color:${th.text}; border-color:${th.border};">
          ${escapeHtml(th.label)}
        </div>`)
      .join('');

    const bodyHtml = `
      <div class="bbqr-field">
        <label class="bbqr-label">Тема оформления</label>
        <div class="bbqr-theme-grid">${themeSwatchesHtml}</div>
        <div class="bbqr-hint">Применяется к окнам «Быстрый ответ» и «Кнопки на главный экран».</div>
      </div>

      <div class="bbqr-field">
        <label class="bbqr-label">Данные скрипта</label>
        <div style="display:flex; gap:10px; flex-wrap:wrap;">
          <button type="button" class="bbqr-btn" id="bbqrExportBtn">Экспорт настроек</button>
          <button type="button" class="bbqr-btn" id="bbqrImportBtn">Импорт настроек</button>
        </div>
        <div class="bbqr-hint">Экспорт сохранит все ответы, папки, кнопки-префиксы и выбранную тему в файл.</div>
      </div>`;

    const footerHtml = `<button type="button" class="bbqr-btn" data-bbqr-cancel>← Назад</button>`;

    const html = overlayShell({ title: 'Настройки', bodyHtml, footerHtml, maxWidth: 560 });
    const { overlay, close } = openOverlay('bbqrSettingsOverlay', html);

    overlay.querySelector('[data-bbqr-cancel]')?.addEventListener('click', () => { close(); renderQuickAnswersOverlay({}); });

    overlay.querySelectorAll('[data-bbqr-theme]').forEach((sw) => {
      sw.addEventListener('click', () => {
        saveTheme(sw.getAttribute('data-bbqr-theme'));
        close();
        renderSettingsOverlay();
      });
    });

    overlay.querySelector('#bbqrExportBtn')?.addEventListener('click', () => exportSettings());
    overlay.querySelector('#bbqrImportBtn')?.addEventListener('click', () => {
      triggerImportFilePicker(() => { close(); renderSettingsOverlay(); });
    });
  }

  function renderPrefixControlOverlay() {
    if (!getThreadIdFromUrl()) { showBbqrToast('Не удалось определить ID темы.', 'error'); return; }

    const stagedPrefixes = loadHomePrefixes();
    const stagedActions = loadHomeActions();
    const actionCards = [['close', 'Закрыть/Открыть тему'], ['stick', 'Закрепить/Открепить тему']];

    const bodyHtml = `
      <div class="bbqr-grid">
        ${PREFIX_MAP.map(([label, id]) => {
          const active = stagedPrefixes.has(label);
          return `
            <div class="bbqr-card" data-prefix-label="${escapeHtml(label)}" data-prefix-id="${id}" style="${active ? 'border-color:var(--bbqr-accent);' : ''}">
              <span class="bbqr-card__pin ${active ? 'is-pinned' : ''}" style="position:static; opacity:1; font-size:13px;">${active ? '✓' : ''}</span>
              <div class="bbqr-card__title">${escapeHtml(label)}</div>
            </div>`;
        }).join('')}
      </div>

      <hr style="border:0; border-top:1px solid var(--bbqr-border); margin:20px 0;" />

      <div class="bbqr-grid" style="grid-template-columns:repeat(auto-fill, minmax(220px,1fr));">
        ${actionCards.map(([key, label]) => {
          const active = stagedActions.has(key);
          return `
            <div class="bbqr-card" data-action-key="${key}" style="${active ? 'border-color:var(--bbqr-accent);' : ''} text-align:center;">
              <div class="bbqr-card__title">${active ? '✓ ' : ''}${label}</div>
            </div>`;
        }).join('')}
      </div>`;

    const footerHtml = `
      <button type="button" class="bbqr-btn" data-bbqr-cancel>Отмена</button>
      <button type="button" class="bbqr-btn bbqr-btn--primary" id="bbqrSavePrefixControl">Сохранить</button>`;

    const html = overlayShell({ title: 'Кнопки на главный экран', bodyHtml, footerHtml, maxWidth: 900 });
    const { overlay, close } = openOverlay('bbqrPrefixControlOverlay', html);

    overlay.querySelector('[data-bbqr-cancel]')?.addEventListener('click', close);

    overlay.querySelectorAll('[data-prefix-label]').forEach((card) => {
      card.addEventListener('click', () => {
        const label = card.getAttribute('data-prefix-label');
        if (stagedPrefixes.has(label)) stagedPrefixes.delete(label); else stagedPrefixes.add(label);
        const active = stagedPrefixes.has(label);
        card.style.borderColor = active ? 'var(--bbqr-accent)' : '';
        const pin = card.querySelector('.bbqr-card__pin');
        if (pin) { pin.classList.toggle('is-pinned', active); pin.textContent = active ? '✓' : ''; }
      });
    });

    overlay.querySelectorAll('[data-action-key]').forEach((card) => {
      card.addEventListener('click', () => {
        const key = card.getAttribute('data-action-key');
        if (stagedActions.has(key)) stagedActions.delete(key); else stagedActions.add(key);
        const active = stagedActions.has(key);
        card.style.borderColor = active ? 'var(--bbqr-accent)' : '';
        const title = card.querySelector('.bbqr-card__title');
        if (title) {
          const label = actionCards.find(([k]) => k === key)?.[1] || '';
          title.textContent = (active ? '✓ ' : '') + label;
        }
      });
    });

    overlay.querySelector('#bbqrSavePrefixControl')?.addEventListener('click', () => {
      saveHomePrefixes(stagedPrefixes);
      saveHomeActions(stagedActions);
      close();
      showBbqrToast('Готово: кнопки сохранены.', 'success');
      setTimeout(() => window.location.reload(), 400);
    });
  }

  function interceptReplySubmit() {
    document.addEventListener(
      'click',
      async (e) => {
        const btn = e.target.closest(REPLY_BTN_SELECTOR);
        if (!btn || bbqrIsApplying) return;

        const settingsToApply = bbLastQuickSettings;
        if (!settingsToApply) {
          setTimeout(() => { window.location.reload(); }, 3000);
          return;
        }

        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();

        bbLastQuickSettings = null;
        bbqrIsApplying = true;

        const originalText = btn.innerHTML;
        btn.innerHTML = '⏳ Применяю настройки...';
        btn.disabled = true;

        try {
          await applyThreadSettingsFromQuickAnswer(settingsToApply);
        } catch (err) {
          console.error('[BB Quick Reply] Ошибка применения настроек:', err);
          showBbqrToast('Ошибка: ' + err.message, 'error');
        } finally {
          bbqrIsApplying = false;
          btn.disabled = false;
          btn.innerHTML = originalText;
          setTimeout(() => { btn.click(); }, 50);
        }
      },
      true
    );
  }

  injectBbqrGlobalStyles();
  migrateData();
  interceptReplySubmit();
  injectDeadlineBadgesInListing();

  let bbqrDeadlineTimer = null;
  const scheduleDeadlineUpdate = () => {
    if (bbqrDeadlineTimer) return;
    bbqrDeadlineTimer = setTimeout(() => {
      bbqrDeadlineTimer = null;
      injectDeadlineBadgesInListing();
    }, 200);
  };

  const deadlineObserver = new MutationObserver(() => scheduleDeadlineUpdate());
  deadlineObserver.observe(document.body, { childList: true, subtree: true });

  let bbqrObserverTimer = null;
  const scheduleDomUpdate = () => {
    if (bbqrObserverTimer) return;
    bbqrObserverTimer = setTimeout(() => {
      bbqrObserverTimer = null;
      ensureInlineAnswerButtons();
    }, 200);
  };

  const observer = new MutationObserver(() => scheduleDomUpdate());
  observer.observe(document.body, { childList: true, subtree: true });

  ensureInlineAnswerButtons();
  injectDeadlineBadgesInListing();
})();