// source --> https://peremena.store/wp-content/themes/peremena/assets/js/max-form-collector.js 
/**
 * MAX Form Collector v4 — глобальная переменная, awaitable отправка
 *
 * Подключение:
 *   <script src="max-form-collector.js"></script>
 *
 * Использование с ожиданием:
 *   MaxFormCollector.init({
 *     proxyUrl: '...',
 *     source: 'peremena',
 *     onSuccess: async ({ formData, sendPromise }) => {
 *       await sendPromise;           // ждём завершения отправки в MAX
 *       await sendToAnotherService(); // другие источники
 *       window.location.href = '/thanks'; // переход
 *     }
 *   });
 */

(function () {
  'use strict';

  // ─── Утилиты ───────────────────────────────────────────────────────────────

  function getFieldLabel(field) {
    if (field.id) {
      const label = document.querySelector(`label[for="${field.id}"]`);
      if (label) return label.textContent.trim().replace(/:$/, '');
    }
    if (field.getAttribute('data-label')) return field.getAttribute('data-label');
    if (field.placeholder)               return field.placeholder;
    if (field.name)                      return field.name.replace(/[_-]/g, ' ');
    return field.type || 'Поле';
  }

  function collectFormData(form) {
    const result = {};
    const seen   = new Set();

    form.querySelectorAll('input, textarea, select').forEach((field) => {
      if (['submit', 'button', 'reset', 'hidden'].includes(field.type)) return;
      if (field.disabled) return;

      const label = getFieldLabel(field);

      if (field.type === 'radio') {
        if (seen.has(field.name)) return;
        seen.add(field.name);
        const checked = form.querySelector(`input[name="${field.name}"]:checked`);
        result[label] = checked ? (checked.value || 'Выбрано') : '—';
        return;
      }

      if (field.type === 'checkbox') {
        result[label] = field.checked ? '✓ Да' : '✗ Нет';
        return;
      }

      if (field.tagName === 'SELECT') {
        const opts = Array.from(field.selectedOptions).map(o => o.text.trim()).filter(Boolean);
        result[label] = opts.length ? opts.join(', ') : '—';
        return;
      }

      result[label] = field.value.trim() || '—';
    });

    return result;
  }

  function formatMessage(formData, formTitle, template) {
    if (typeof template === 'function') return template(formData, formTitle);

    const ts = new Date().toLocaleString('ru-RU', {
      day: '2-digit', month: '2-digit', year: 'numeric',
      hour: '2-digit', minute: '2-digit',
    });

    const rows = Object.entries(formData)
      .map(([k, v]) => `**${k}:** ${v}`)
      .join('\n');

    return `**${formTitle || 'Новая заявка'}**\n_${ts}_\n\n${rows}\n\n_Сайт: ${location.hostname}_`;
  }

  // ─── Кнопка ────────────────────────────────────────────────────────────────

  function setBtn(btn, state) {
    if (!btn) return;
    const orig = btn.getAttribute('data-orig') || btn.textContent.trim();
    btn.setAttribute('data-orig', orig);
    const map = {
      loading: ['⏳ Отправка…',  true ],
      success: ['✅ Отправлено!', true ],
      error:   ['❌ Ошибка',      false],
      default: [orig,             false],
    };
    const [text, disabled] = map[state] || map.default;
    btn.textContent = text;
    btn.disabled    = disabled;
    if (state === 'success' || state === 'error') {
      setTimeout(() => setBtn(btn, 'default'), 3000);
    }
  }

  // ─── Отправка в MAX через Worker ───────────────────────────────────────────

  function sendToProxy(text, source, options) {
    const body = { text, format: 'markdown', source };
    if (options.chatId) body.chat_id = options.chatId;

    return fetch(options.proxyUrl, {
      method:  'POST',
      headers: { 'Content-Type': 'application/json' },
      body:    JSON.stringify(body),
    }).then(res =>
      res.json().catch(() => ({})).then(json => {
        if (!res.ok) {
          throw new Error(`Worker error ${res.status}: ${json.error || json.message || res.statusText}`);
        }
        return json;
      })
    );
  }

  // ─── Навешиваем на форму ───────────────────────────────────────────────────

  function attachForm(form, options) {
    form.addEventListener('submit', function (e) {
      e.preventDefault();

      const btn = form.querySelector('[type="submit"]');

      const source =
        form.getAttribute('data-max-source') ||
        options.source || '';

      const formTitle =
        form.getAttribute('data-max-title') ||
        (form.querySelector('h1,h2,h3,legend') || {}).textContent ||
        document.title;

      const formData = collectFormData(form);
      const message  = formatMessage(formData, formTitle.trim ? formTitle.trim() : formTitle, options.messageTemplate);

      setBtn(btn, 'loading');

      // Промис отправки в MAX — его можно await-ить в onSuccess
      var sendPromise = sendToProxy(message, source, options);

      sendPromise
        .then(function (result) {
          setBtn(btn, 'success');
          if (options.resetOnSuccess !== false) form.reset();

          if (typeof options.onSuccess === 'function') {
            // Передаём sendPromise чтобы внешний код мог убедиться что MAX получил
            options.onSuccess({ formData, message, form, source, result, sendPromise });
          }
        })
        .catch(function (err) {
          console.error('[MaxFormCollector]', err);
          setBtn(btn, 'error');
          if (typeof options.onError === 'function') {
            options.onError(err, form);
          }
        });

      // Возвращаем промис — полезно если слушаете submit снаружи
      return sendPromise;
    });
  }

  // ─── Публичный API ─────────────────────────────────────────────────────────

  var MaxFormCollector = {

    /**
     * Инициализирует сборщик форм.
     *
     * @param {object}   options
     * @param {string}   options.proxyUrl          — URL Cloudflare Worker
     * @param {string}   [options.source]           — ключ маршрута
     * @param {number}   [options.chatId]           — явный chat_id
     * @param {string}   [options.forms]            — CSS-селектор форм
     * @param {Function} [options.messageTemplate]  — (data, title) => string
     * @param {boolean}  [options.resetOnSuccess]   — сбросить форму (true)
     * @param {Function} [options.onSuccess]        — ({ formData, message, form, source, result, sendPromise }) => void|Promise
     * @param {Function} [options.onError]          — (err, form) => void
     */
    init: function (options) {
      if (!options || !options.proxyUrl) {
        console.error('[MaxFormCollector] Укажите proxyUrl');
        return;
      }

      var selector = options.forms || 'form';
      var forms    = document.querySelectorAll(selector);

      if (!forms.length) {
        console.warn('[MaxFormCollector] Нет форм по селектору "' + selector + '"');
        return;
      }

      forms.forEach(function (f) { attachForm(f, options); });
      console.info('[MaxFormCollector] Готов. Форм: ' + forms.length + ', source: "' + (options.source || '—') + '"');
    },

    /**
     * Собрать данные формы без отправки (для отладки).
     * @param {HTMLFormElement} formElement
     * @returns {object}
     */
    collect: collectFormData,

    /**
     * Отправить произвольный текст напрямую (без формы).
     * Возвращает Promise.
     * @param {string} text
     * @param {object} options  — proxyUrl, source, chatId
     * @returns {Promise}
     */
    send: function (text, options) {
      return sendToProxy(text, options.source || '', options);
    },
  };

  // Делаем глобальной переменной
  window.MaxFormCollector = MaxFormCollector;

})();
// source --> https://peremena.store/wp-content/themes/peremena/assets/js/peremena-chat.js?v=1785412481 
(function () {
  "use strict";

  /* ─── Config ─────────────────────────────────────── */
  const API_BASE = "https://chat.peremena.store";
  const LS_KEY_SID = "prmn_chat_sid";
  const LS_KEY_LED = "prmn_lead_done";
  const LS_KEY_HISTORY = "prmn_chat_history";
  const LS_KEY_LEAD_FORM_PENDING = "prmn_lead_form_pending";
  const LS_KEY_LEAD_FORM_CLOSED = "prmn_lead_form_closed";
  const HISTORY_LIMIT = 100;
  const PRIVACY_URL = "#";

  const WELCOME_TEXT =
    "Здравствуйте. Я, Виктория, консультант Перемены. Подберу решение под вашу задачу: сайт, реклама, бренд или маркетинговая стратегия. С чего начнём?";
  const HEADER_TITLE = "консультант<br>виктория";
  const INPUT_PLACEHOLDER = "текст сообщения";
  const LEAD_CONFIRM_TEXT =
    "Спасибо. Передала Дмитрию, он свяжется с вами в рабочее время";

  const AUTO_OPEN_DELAY_MS = 30000;
  const AUTO_REOPEN_DELAY_MS = 180000; // 3 мин после закрытия без сообщений
  const LAUNCHER_HINT_DELAY_MS = 1200;
  const FORM_SHOW_DELAY_MS = 300;
  const WIDGET_ANIM_MS = 480;
  const OVERLAY_ANIM_MS = 420;
  const LEAD_FORM_TITLE = "я виктория<br>консультант перемены";
  const LEAD_FORM_DESC =
    "Подберу решение под вашу задачу: сайт, реклама, бренд или маркетинговая стратегия. С чего начнём?";
  const REOPEN_FORM_BTN_TEXT = "оставить контакты";
  const MAX_ATTACHMENTS = 10;

  /* ─── Session ID ─────────────────────────────────── */
  function getSessionId() {
    // sessionStorage, не localStorage: каждый новый визит (новая вкладка/сессия браузера) =
    // новый session_id = чистый чат у клиента. В рамках одного визита переходы между
    // страницами в той же вкладке сохраняют диалог. На бэкенде все сессии пишутся для админки.
    let sid = sessionStorage.getItem(LS_KEY_SID);
    if (!sid) {
      sid = crypto.randomUUID();
      sessionStorage.setItem(LS_KEY_SID, sid);
    }
    return sid;
  }

  const SESSION_ID = getSessionId();

  function isLeadSubmitted() {
    return localStorage.getItem(LS_KEY_LED) === "1";
  }

  function markLeadSubmitted() {
    localStorage.setItem(LS_KEY_LED, "1");
    clearLeadFormState();
  }

  function isLeadFormPending() {
    return (
      !isLeadSubmitted() &&
      localStorage.getItem(LS_KEY_LEAD_FORM_PENDING) === SESSION_ID
    );
  }

  function markLeadFormPending() {
    if (isLeadSubmitted()) return;
    leadFormAvailable = true;
    localStorage.setItem(LS_KEY_LEAD_FORM_PENDING, SESSION_ID);
    localStorage.removeItem(LS_KEY_LEAD_FORM_CLOSED);
  }

  function markLeadFormClosed() {
    if (!isLeadFormPending()) return;
    localStorage.setItem(LS_KEY_LEAD_FORM_CLOSED, "1");
  }

  function clearLeadFormState() {
    leadFormAvailable = false;
    localStorage.removeItem(LS_KEY_LEAD_FORM_PENDING);
    localStorage.removeItem(LS_KEY_LEAD_FORM_CLOSED);
  }

  function restoreLeadFormUi() {
    if (!messages) return;

    if (isLeadSubmitted()) {
      clearLeadFormState();
      if (leadOverlay) {
        leadOverlay.classList.remove("prmn-lead-overlay--visible");
        leadOverlay.hidden = true;
      }
      removeFormReopenButton();
      return;
    }

    leadFormAvailable = isLeadFormPending();
    if (!leadFormAvailable) return;

    if (leadOverlay) {
      leadOverlay.classList.remove("prmn-lead-overlay--visible");
      leadOverlay.hidden = true;
    }
    showFormReopenButton();
  }

  /* ─── Styles ─────────────────────────────────────── */
  const CSS = `
.prmn *,
.prmn *::before,
.prmn *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font-family: Inter, sans-serif;
}

.prmn-launcher-wrap {
  position: fixed;
  right: 2rem;
  bottom: 2rem;
  z-index: 999999;
  display: flex;
  align-items: center;
  gap: 1rem;
  transform-origin: bottom right;
  transition:
    opacity 0.38s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.38s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0.38s ease;
}
.prmn-launcher-wrap--hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: scale(0.88);
}
@keyframes prmn-launcher-label-in {
  from {
    opacity: 0;
    transform: translateX(1.6rem) scale(0.96);
  }
  to {
    opacity: 1;
    transform: translateX(0) scale(1);
  }
}

@keyframes prmn-launcher-btn-pulse {
  0%,
  100% {
    box-shadow: 0 4px 60px rgba(0, 0, 0, 0.1);
  }
  50% {
    box-shadow:
      0 4px 60px rgba(55, 158, 152, 0.28),
      0 0 0 0.6rem rgba(55, 158, 152, 0.08);
  }
}

@media (any-hover: hover) {
  .prmn-launcher-wrap--hint-done:hover .prmn-launcher-label,
  .prmn-launcher-wrap:hover .prmn-launcher-label {
    transform: translateX(0);
    opacity: 1;
    visibility: visible;
  }
}
.prmn-launcher-label {
  position: absolute;
  right: calc(100% + 1rem);
  top: 0;
  white-space: nowrap;
  font-family: var(--font-family);
  font-weight: 300;
  font-size: 2.4rem;
  line-height: 120%;
  text-transform: lowercase;
  color: var(--black);
  padding: 1.4rem 3rem 1.72rem;
  border-radius: 2rem;
  background: var(--white);
  box-shadow: 0 4px 60px rgba(0, 0, 0, 0.1);
  transform: translateX(1rem);
  opacity: 0;
  visibility: hidden;
  pointer-events: auto;
  cursor: pointer;
  border: none;
  font: inherit;
  transition:
    transform 0.35s cubic-bezier(0.22, 1, 0.36, 1),
    opacity 0.35s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0.35s ease;
}
.prmn-launcher-label:focus-visible {
  outline: 2px solid rgba(55, 158, 152, 0.45);
  outline-offset: 2px;
}
.prmn-launcher-label.prmn-launcher-label--intro {
  transform: translateX(0);
  opacity: 1;
  visibility: visible;
  animation: prmn-launcher-label-in 0.55s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.prmn-launcher-wrap--hint-active .prmn-launcher-btn {
  animation: prmn-launcher-btn-pulse 2.2s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .prmn-launcher-label.prmn-launcher-label--intro {
    animation: none;
  }
  .prmn-launcher-wrap--hint-active .prmn-launcher-btn {
    animation: none;
    box-shadow:
      0 4px 60px rgba(0, 0, 0, 0.1),
      0 0 0 0.4rem rgba(55, 158, 152, 0.12);
  }
}
.prmn-launcher-btn {
  width: 6rem;
  height: 6rem;
  border: none;
  border-radius: 2rem;
  background: var(--white);
  cursor: pointer;
  box-shadow: 0 4px 60px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--black);
  transition:
    color 0.3s ease,
    box-shadow 0.3s ease;
}
.prmn-launcher-btn svg {
  width: 3rem;
  height: 3rem;
  fill: currentColor;
}
.prmn-launcher-btn:hover {
  color: var(--accent-cyan);
}

.prmn-widget {
  position: fixed;
  right: 2rem;
  bottom: 2rem;
  width: 56rem;
  height: 72rem;
  max-height: 76vh;
  background: #fff;
  border-radius: 4rem;
  box-shadow: 0 4px 60px rgba(0, 0, 0, 0.1);
  z-index: 999999;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transform: scale(0.72);
  transform-origin: bottom right;
  transition:
    opacity 0.48s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.48s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0.48s ease;
  will-change: transform, opacity;
}
.prmn-widget.prmn-active {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
  transform: scale(1);
}
@media (max-width: 600px) {
  .prmn-widget {
    width: 100%;
    left: 0;
    right: 0;
    top: 0;
    bottom: auto;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    transform: translateY(100%);
    transform-origin: bottom center;
  }
  .prmn-widget.prmn-active {
    transform: translateY(0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .prmn-launcher-wrap,
  .prmn-widget,
  .prmn-lead-overlay,
  .prmn-lead-overlay__card {
    transition: none !important;
  }
}

.prmn-header {
  padding: 4rem;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-shrink: 0;
  gap: 1rem;
}
.prmn-header-main {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 2rem;
}
.prmn-avatar {
  width: 10rem;
  height: 10rem;
  border-radius: 2rem;
  overflow: hidden;
}
.prmn-avatar img {
  display: block;
  width: 100%;
  height: 100%;
}
.prmn-title {
  font-family: var(--font-family);
  font-weight: 400;
  font-size: 3.6rem;
  line-height: 110%;
  text-transform: lowercase;
  color: var(--black);
}
.prmn-close-btn {
  border: none;
  background: none;
  width: 2.8rem;
  height: 2.8rem;
  flex-shrink: 0;
  cursor: pointer;
  padding: 0;
  color: var(--black);
  transition: color 0.3s ease;
}
.prmn-close-btn svg {
  display: block;
  width: 100%;
  height: 100%;
  stroke: currentColor;
}
@media (any-hover: hover) {
  .prmn-close-btn:hover {
    color: var(--accent-cyan);
  }
}

.prmn-offline {
  margin: 0 4rem 2rem;
  padding: 2rem 3rem;
  border-radius: 2rem;
  background: #fff5f5;
  font-size: 1.6rem;
  line-height: 140%;
  color: #900;
}
.prmn-offline[hidden] {
  display: none;
}

.prmn-chat-screen {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  position: relative;
}

.prmn-messages {
  flex: 1;
  overflow-y: auto;
  margin: 0 4rem 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
@media (max-width: 600px) {
  .prmn-title {
    font-size: 3rem;
    max-width: 100vw;
  }
  .prmn-chat-footer {
    padding: 2rem 4rem 3rem;
  }
  .prmn-chat-input,
  .prmn-form__input {
    font-size: 16px;
  }
}

.prmn-msg-row {
  position: relative;
  max-width: 85%;
  display: flex;
  flex-direction: column;
}
.prmn-msg-row-user {
  align-self: flex-end;
  align-items: flex-end;
}
.prmn-msg-row-assistant {
  align-self: flex-start;
  align-items: flex-start;
}

.prmn-msg {
  padding: 1.8rem 3rem;
  border-radius: 2rem;
  font-weight: 200;
  font-size: 1.8rem;
  line-height: 120%;
  color: var(--black);
  white-space: pre-wrap;
  word-break: break-word;
}
.prmn-msg-assistant {
  background: var(--accent-cyan);
  color: var(--white);
  font-weight: 300;
}
.prmn-msg-user {
  background: #f7f7f7;
  color: var(--black);
}

.prmn-msg-reaction {
  position: absolute;
  bottom: -0.6rem;
  right: -0.4rem;
  font-size: 1.8rem;
  line-height: 1;
  background: #fff;
  border-radius: 1.2rem;
  padding: 0.2rem 0.6rem;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  pointer-events: none;
}

.prmn-msg-files {
  margin-top: 0.6rem;
  font-size: 1.4rem;
  opacity: 0.75;
}

.prmn-form-reopen {
  align-self: flex-start;
  margin-top: 0.5rem;
  padding: 1.6rem 3rem;
  border: none;
  border-radius: 2rem;
  font-size: 1.8rem;
  line-height: 120%;
  font-weight: 200;
  cursor: pointer;
  text-transform: lowercase;
}

.prmn-lead-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(4px);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity 0.42s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0.42s ease;
}
.prmn-lead-overlay[hidden] {
  display: none;
}
.prmn-lead-overlay.prmn-lead-overlay--visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
.prmn-lead-overlay__card {
  position: relative;
  width: 100%;
  max-height: 100%;
  height: 100%;
  overflow-y: auto;
  padding: 3rem 4rem 4rem;
  border-radius: 4rem;
  background: #fff;
  box-shadow: 0 4px 60px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
  opacity: 0;
  transform: translateY(2.4rem) scale(0.97);
  transform-origin: bottom center;
  transition:
    opacity 0.42s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.42s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform, opacity;
}
.prmn-lead-overlay.prmn-lead-overlay--visible .prmn-lead-overlay__card {
  opacity: 1;
  transform: translateY(0) scale(1);
}
@media (max-width: 600px) {
  .prmn-lead-overlay__card {
    transform: translateY(100%);
    transform-origin: bottom center;
  }
  .prmn-lead-overlay.prmn-lead-overlay--visible .prmn-lead-overlay__card {
    transform: translateY(0);
  }
}
.prmn-lead-overlay__close {
  position: absolute;
  top: 2rem;
  right: 2rem;
  border: none;
  background: none;
  width: 2.8rem;
  height: 2.8rem;
  cursor: pointer;
  padding: 0;
  color: var(--black);
}
.prmn-lead-overlay__close svg {
  display: block;
  width: 100%;
  height: 100%;
  stroke: currentColor;
}
.prmn-lead-overlay__title {
  font-weight: 700;
  font-weight: 400;
  font-size: 3.6rem;
  line-height: 110%;
  text-transform: lowercase;
  color: var(--black);
  padding-right: 4rem;
  margin-bottom: 1rem;
}
.prmn-lead-overlay__desc {
  font-family: var(--font-family);
  font-weight: 200;
  font-size: 2rem;
  line-height: 120%;
  color: var(--black);
  margin-bottom: 4rem;
}
.prmn-form {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2rem;
  --gap: -0.5rem;
}
.prmn-form__field {
  display: block;
  width: 100%;
  position: relative;
}
.prmn-form__field-wrapper {
  display: block;
  height: 6rem;
  position: relative;
  border-radius: 2rem;
  outline: 2px solid transparent;
  padding: 1.5rem 3rem;
  background-color: #f7fafa;
  font-weight: 300;
  font-size: 2rem;
  line-height: 150%;
  text-transform: lowercase;
  overflow: hidden;
  transition:
    color 0.3s ease-out,
    background-color 0.3s ease-out,
    outline-color 0.3s ease-in;
}
.prmn-form__field-wrapper--area {
  height: 14rem;
}
.prmn-form__field.filled .prmn-form__label {
  font-size: 1.4rem;
  transform: translateY(var(--gap));
}
.prmn-form__field.filled .prmn-form__input {
  transform: translateY(-32%);
}
.prmn-form__field.filled .prmn-form__input--area {
  transform: none;
}
.prmn-form__field.focus .prmn-form__field-wrapper {
  outline-color: #000;
  background-color: #fff;
}
.prmn-form__field.focus .prmn-form__label {
  background-color: #fff;
}
.prmn-form__label {
  position: absolute;
  z-index: 4;
  background-color: #f7fafa;
  width: calc(100% - 6rem);
  top: 0;
  padding-top: 1.4rem;
  left: 3rem;
  font-weight: 300;
  font-size: 2rem;
  line-height: 150%;
  text-transform: lowercase;
  color: rgba(0, 0, 0, 0.25);
  transition:
    transform 0.3s ease-in,
    font-size 0.3s ease-in,
    background-color 0.3s ease-in,
    color 0.3s ease-out;
  pointer-events: none;
}
.prmn-form__input {
  display: block;
  position: absolute;
  padding: 1.5rem 3rem;
  height: 100%;
  top: 50%;
  left: 0;
  transform: translateY(-50%);
  border-radius: 2rem;
  width: 100%;
  border: none;
  background: none;
  outline: none;
  font-weight: 300;
  font-size: 2rem;
  line-height: 150%;
  text-transform: lowercase;
  color: var(--black);
  transition:
    transform 0.3s ease-in,
    background-color 0.3s ease-in;
}
.prmn-form__input:focus {
  transform: translateY(-32%);
}
.prmn-form__input:focus ~ .prmn-form__label {
  font-size: 1.4rem;
  transform: translateY(var(--gap));
}
.prmn-form__input--area {
  height: 100%;
  resize: none;
  top: 0;
  transform: none;
  padding-top: 3rem;
}
.prmn-form__input--area:focus {
  transform: none;
}
.prmn-form__error {
  display: none;
  color: #d00;
  font-size: 1.4rem;
  margin-bottom: -1rem;
}
.prmn-form__error.prmn-visible {
  display: block;
}
.prmn-checkbox {
  display: flex;
  gap: 1rem;
  font-size: 1.3rem;
  line-height: 1.4;
  align-items: flex-start;
  cursor: pointer;
  user-select: none;
}
.prmn-checkbox__field {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.prmn-checkbox__box {
  width: 3rem;
  height: 3rem;
  flex-shrink: 0;
  margin-top: 0.1rem;
  border: 2px solid var(--black);
  border-radius: 0.8rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  transition:
    background-color 0.2s ease,
    border-color 0.2s ease;
}
.prmn-checkbox__icon {
  width: 1.6rem;
  height: 1.6rem;
  color: var(--white);
  opacity: 0;
  transform: scale(0.85);
  transition:
    opacity 0.2s ease,
    transform 0.2s ease;
}
.prmn-checkbox__field:checked + .prmn-checkbox__box {
  background: var(--accent-cyan);
  border-color: var(--accent-cyan);
}
.prmn-checkbox__field:checked + .prmn-checkbox__box .prmn-checkbox__icon {
  opacity: 1;
  transform: scale(1);
}
.prmn-checkbox__field:focus-visible + .prmn-checkbox__box {
  outline: 2px solid var(--black);
  outline-offset: 2px;
}
.prmn-checkbox__text {
  font-weight: 200;
  font-size: 1.4rem;
  line-height: 120%;
  text-transform: lowercase;
  color: var(--black);
  flex: 1;
  min-width: 0;
}
.prmn-checkbox__text a {
  color: inherit;
}
.prmn-form__submit {
  width: 100%;
  margin-top: 0.5rem;
  padding: 1.5rem 3rem 1.6rem;
  border: none;
  border-radius: 2rem;
  font-weight: 300;
  font-size: 2.4rem;
  line-height: 120%;
  text-transform: lowercase;
  color: var(--white);
  background-color: var(--accent-cyan);
  cursor: pointer;
  transition: background-color 0.3s ease-out;
}
.prmn-form__submit:disabled {
  background-color: #e3e6e6;
  color: #adafb2;
  cursor: not-allowed;
}

.prmn-chat-footer {
  padding: 4rem;
  display: flex;
  gap: 1rem;
  flex-shrink: 0;
  align-items: flex-end;
}
.prmn-input-col {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}
.prmn-attachments-bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.8rem 1.6rem;
  border-radius: 1.4rem;
  background: #eef6f6;
  font-size: 1.4rem;
  line-height: 120%;
  color: var(--black);
  text-transform: lowercase;
}
.prmn-attachments-bar[hidden] {
  display: none;
}
.prmn-attachments-clear {
  margin-left: auto;
  border: none;
  background: none;
  padding: 0;
  font-size: 1.4rem;
  text-transform: lowercase;
  color: rgba(0, 0, 0, 0.45);
  text-decoration: underline;
  cursor: pointer;
}
.prmn-input-wrap {
  position: relative;
  flex: 1;
  min-width: 0;
}
.prmn-chat-input {
  width: 100%;
  border: none;
  background: #f7f7f7;
  height: 6rem;
  padding: 2rem 5.6rem 2rem 3rem;
  border-radius: 2rem;
  outline: none;
  font-size: 2rem;
}
.prmn-attach-btn {
  position: absolute;
  top: 50%;
  right: 2rem;
  transform: translateY(-50%);
  border: none;
  background: none;
  padding: 0;
  width: 2.2rem;
  height: 3rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.prmn-attach-btn svg {
  display: block;
  width: 2.2rem;
  height: 3rem;
}
.prmn-file-input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.prmn-send-btn {
  width: 6rem;
  height: 6rem;
  border: none;
  border-radius: 2rem;
  cursor: pointer;
  background: #f7f7f7;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--black);
  transition: color 0.3s ease;
}
.prmn-send-btn:hover {
  color: var(--accent-cyan);
}
.prmn-send-btn svg {
  width: 3rem;
  height: 3rem;
  fill: currentColor;
}
.prmn-send-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}


  `;

  const HTML = `
    <div class="prmn">
      <div class="prmn-launcher-wrap" id="prmn-launcher">
        <div class="prmn-launcher-label" id="prmn-launcher-label" role="button" tabindex="0" aria-label="Открыть чат">задайте любой вопрос</div>
        <button class="prmn-launcher-btn" id="prmn-open" aria-label="Открыть чат">
          <svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M30 25.2094C30 27.194 30 28.1863 29.6498 28.7763C29.183 29.5629 28.3238 30.0317 27.4096 29.9987C26.724 29.9739 25.8896 29.4369 24.2207 28.3629L17.761 24.2059C17.4546 24.0088 17.3015 23.9102 17.1387 23.8358C16.9216 23.7365 16.6915 23.6689 16.4553 23.6349C16.2781 23.6094 16.0959 23.6094 15.7316 23.6094H3.75C2.58515 23.6094 2.00272 23.6094 1.54329 23.4191C0.930721 23.1653 0.444036 22.6787 0.190301 22.0661C0 21.6067 0 21.0242 0 19.8594V3.75C0 2.58515 0 2.00272 0.190301 1.54329C0.444036 0.930721 0.930721 0.444036 1.54329 0.190301C2.00272 0 2.58515 0 3.75 0H26.25C27.4149 0 27.9973 0 28.4567 0.190301C29.0693 0.444036 29.556 0.930721 29.8097 1.54329C30 2.00272 30 2.58515 30 3.75V25.2094Z" />
          </svg>
        </button>
      </div>

      <div class="prmn-widget" id="prmn-widget" role="dialog" aria-label="Чат с консультантом" aria-hidden="true">
        <div class="prmn-chat-screen" id="prmn-chat-screen">
          <div class="prmn-header">
            <div class="prmn-header-main">
              <div class="prmn-avatar">
                <img src="https://peremena.store/wp-content/themes/peremena/assets/img/vika.webp" width="100" height="100">
              </div>
              <div class="prmn-title">${HEADER_TITLE}</div>
            </div>
            <button type="button" class="prmn-close-btn prmn-js-close" aria-label="Закрыть">
              <svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M1 1L29 29M29 1L1 29" stroke-width="2" stroke-linecap="round" />
              </svg>
            </button>
          </div>
          <div class="prmn-offline" id="prmn-offline" hidden>
            Сервис временно недоступен. Попробуйте позже.
          </div>
          <div class="prmn-messages" id="prmn-messages"></div>
          <div class="prmn-chat-footer">
            <div class="prmn-input-col">
              <div class="prmn-attachments-bar" id="prmn-attachments-bar" hidden>
                <span id="prmn-attachments-label"></span>
                <button type="button" class="prmn-attachments-clear" id="prmn-attachments-clear">очистить</button>
              </div>
              <div class="prmn-input-wrap">
                <input class="prmn-chat-input" id="prmn-chat-input" type="text" placeholder="${INPUT_PLACEHOLDER}" maxlength="4000">
                <button type="button" class="prmn-attach-btn" id="prmn-attach-btn" aria-label="Прикрепить файл">
                  <svg width="22" height="30" viewBox="0 0 22 30" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M17.1992 0C19.3138 0.00011028 21.0282 1.71456 21.0283 3.8291V26.1709C21.0283 28.2856 19.3139 29.9999 17.1992 30H9.6416C7.52686 30 5.8125 28.2856 5.8125 26.1709L5.8125 10.1514C5.81269 8.03678 7.52697 6.32227 9.6416 6.32227H10.9922C13.1067 6.32242 14.8211 8.03687 14.8213 10.1514L14.8213 20.3865C14.8213 20.8483 14.4469 21.2227 13.9852 21.2227H13.5734C13.1116 21.2227 12.7373 20.8483 12.7373 20.3865L12.7373 10.1514C12.7371 9.18786 11.9557 8.40641 10.9922 8.40625H9.6416C8.67796 8.40625 7.89667 9.18777 7.89648 10.1514L7.89648 26.1709C7.89648 27.1347 8.67785 27.916 9.6416 27.916H17.1992C18.1629 27.9159 18.9443 27.1346 18.9443 26.1709V3.8291C18.9442 2.86555 18.1628 2.08409 17.1992 2.08398L3.8291 2.08398C2.86544 2.08398 2.08414 2.86548 2.08398 3.8291L2.08398 20.3865C2.08398 20.8483 1.70964 21.2227 1.24787 21.2227H0.836119C0.374343 21.2227 0 20.8483 0 20.3865L0 3.8291C0.000156361 1.71449 1.71445 0 3.8291 0L17.1992 0Z" fill="#FCFFFF"/>
                    <path d="M17.1992 0C19.3138 0.00011028 21.0282 1.71456 21.0283 3.8291V26.1709C21.0283 28.2856 19.3139 29.9999 17.1992 30H9.6416C7.52686 30 5.8125 28.2856 5.8125 26.1709L5.8125 10.1514C5.81269 8.03678 7.52697 6.32227 9.6416 6.32227H10.9922C13.1067 6.32242 14.8211 8.03687 14.8213 10.1514L14.8213 20.3865C14.8213 20.8483 14.4469 21.2227 13.9852 21.2227H13.5734C13.1116 21.2227 12.7373 20.8483 12.7373 20.3865L12.7373 10.1514C12.7371 9.18786 11.9557 8.40641 10.9922 8.40625H9.6416C8.67796 8.40625 7.89667 9.18777 7.89648 10.1514L7.89648 26.1709C7.89648 27.1347 8.67785 27.916 9.6416 27.916H17.1992C18.1629 27.9159 18.9443 27.1346 18.9443 26.1709V3.8291C18.9442 2.86555 18.1628 2.08409 17.1992 2.08398L3.8291 2.08398C2.86544 2.08398 2.08414 2.86548 2.08398 3.8291L2.08398 20.3865C2.08398 20.8483 1.70964 21.2227 1.24787 21.2227H0.836119C0.374343 21.2227 0 20.8483 0 20.3865L0 3.8291C0.000156361 1.71449 1.71445 0 3.8291 0L17.1992 0Z" fill="black" fill-opacity="0.25"/>
                  </svg>
                </button>
                <input type="file" class="prmn-file-input" id="prmn-file-input" multiple>
              </div>
            </div>
            <button type="button" class="prmn-send-btn" id="prmn-send-btn" aria-label="Отправить">
              <svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M28.6178 12.7628C30.4608 13.6839 30.4607 16.3139 28.6177 17.2349L3.62398 29.7256C1.60294 30.7356 -0.614708 28.759 0.157178 26.6355L4.0768 15.8529C4.27734 15.3012 4.27734 14.6965 4.0768 14.1449L0.157148 3.36192C-0.614727 1.2385 1.60293 -0.738137 3.62396 0.271894L28.6178 12.7628Z" />
              </svg>
            </button>
          </div>
          <div class="prmn-lead-overlay" id="prmn-lead-overlay" hidden>
            <div class="prmn-lead-overlay__card">
              <button type="button" class="prmn-lead-overlay__close prmn-js-lead-close" aria-label="Закрыть форму">
                <svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <path d="M1 1L29 29M29 1L1 29" stroke-width="2" stroke-linecap="round"/>
                </svg>
              </button>
              <div class="prmn-lead-overlay__title">${LEAD_FORM_TITLE}</div>
              <div class="prmn-lead-overlay__desc">${LEAD_FORM_DESC}</div>
              <form class="prmn-form" id="prmn-lead-form" novalidate>
                <div class="prmn-form__error" id="prmn-lead-error"></div>
                <div class="prmn-form__field" data-prmn-field>
                  <div class="prmn-form__field-wrapper">
                    <input type="text" class="prmn-form__input" id="prmn-lead-name" name="name" autocomplete="name">
                    <label class="prmn-form__label" for="prmn-lead-name">как ваше имя?</label>
                  </div>
                </div>
                <div class="prmn-form__field" data-prmn-field>
                  <div class="prmn-form__field-wrapper">
                    <input type="tel" class="prmn-form__input" id="prmn-lead-phone" name="phone" autocomplete="tel">
                    <label class="prmn-form__label" for="prmn-lead-phone">ваш телефон</label>
                  </div>
                </div>
                <div class="prmn-form__field" data-prmn-field>
                  <div class="prmn-form__field-wrapper prmn-form__field-wrapper--area">
                    <textarea class="prmn-form__input prmn-form__input--area" id="prmn-lead-message" name="message" rows="4"></textarea>
                    <label class="prmn-form__label" for="prmn-lead-message">введите текст сообщения</label>
                  </div>
                </div>
                <label class="prmn-checkbox">
                  <input type="checkbox" class="prmn-checkbox__field" id="prmn-lead-consent" checked>
                  <span class="prmn-checkbox__box" aria-hidden="true">
                    <svg class="prmn-checkbox__icon" viewBox="0 0 12 10" fill="none" xmlns="http://www.w3.org/2000/svg">
                      <path d="M1 5.5L4.5 9L11 1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
                    </svg>
                  </span>
                  <span class="prmn-checkbox__text">отправляя сообщение вы соглашаетесь с <a href="${PRIVACY_URL}" target="_blank" rel="noopener">политикой обработки данных</a></span>
                </label>
                <button type="submit" class="prmn-form__submit" id="prmn-lead-submit" disabled>отправить сообщение</button>
              </form>
            </div>
          </div>
        </div>
      </div>
    </div>
  `;

  /* ─── State ──────────────────────────────────────── */
  let launcher,
    launcherLabel,
    widget,
    messages,
    chatInput,
    sendBtn,
    offlineBanner,
    leadOverlay,
    leadForm,
    attachBtn,
    fileInput,
    attachmentsBar,
    attachmentsLabel,
    attachmentsClearBtn;
  /** @type {{ role: string, content: string, ts?: string|null, reaction?: string, filesCount?: number }[]} */
  let chatMessages = [];
  let welcomeShown = false;
  let historySyncing = false;
  let dialogStarted = false;
  let pendingShowForm = false;
  let leadFormAvailable = false;
  let formReopenEl = null;
  let formShowTimer = null;
  let lastUserBubbleRow = null;
  let isOffline = false;
  let isStreaming = false;
  let autoOpenTimer = null;
  let autoReopenTimer = null;
  let launcherHintTimer = null;
  let launcherHintDismissed = false;
  /** @type {File[]} */
  let pendingAttachments = [];

  /* ─── Mount & refs ───────────────────────────────── */
  function mount() {
    const style = document.createElement("style");
    style.textContent = CSS;
    document.head.appendChild(style);
    const wrap = document.createElement("div");
    wrap.innerHTML = HTML;
    document.body.appendChild(wrap);
  }

  function bindRefs() {
    launcher = document.getElementById("prmn-launcher");
    launcherLabel = document.getElementById("prmn-launcher-label");
    widget = document.getElementById("prmn-widget");
    messages = document.getElementById("prmn-messages");
    chatInput = document.getElementById("prmn-chat-input");
    sendBtn = document.getElementById("prmn-send-btn");
    offlineBanner = document.getElementById("prmn-offline");
    leadOverlay = document.getElementById("prmn-lead-overlay");
    leadForm = document.getElementById("prmn-lead-form");
    attachBtn = document.getElementById("prmn-attach-btn");
    fileInput = document.getElementById("prmn-file-input");
    attachmentsBar = document.getElementById("prmn-attachments-bar");
    attachmentsLabel = document.getElementById("prmn-attachments-label");
    attachmentsClearBtn = document.getElementById("prmn-attachments-clear");
  }

  function scrollMessages() {
    if (!messages) return;
    requestAnimationFrame(() => {
      messages.scrollTop = messages.scrollHeight;
    });
  }

  /** На мобильных поджимаем виджет к visualViewport, чтобы лента не уезжала под клавиатуру */
  function bindMobileViewport() {
    const isMobile = () => window.matchMedia("(max-width: 600px)").matches;

    const applyViewport = () => {
      if (!widget) return;

      if (!isMobile() || !widget.classList.contains("prmn-active")) {
        widget.style.top = "";
        widget.style.height = "";
        widget.style.maxHeight = "";
        return;
      }

      const vv = window.visualViewport;
      if (!vv) return;

      widget.style.top = `${vv.offsetTop}px`;
      widget.style.height = `${vv.height}px`;
      widget.style.maxHeight = `${vv.height}px`;
      scrollMessages();
    };

    if (window.visualViewport) {
      window.visualViewport.addEventListener("resize", applyViewport);
      window.visualViewport.addEventListener("scroll", applyViewport);
    }

    window.addEventListener("resize", applyViewport);

    chatInput.addEventListener("focus", () => {
      applyViewport();
      setTimeout(applyViewport, 100);
      setTimeout(scrollMessages, 150);
      setTimeout(scrollMessages, 350);
    });

    chatInput.addEventListener("blur", () => {
      setTimeout(applyViewport, 120);
    });
  }

  /* ─── Phone mask (lead form) ─────────────────────── */
  function applyPhoneMask(input) {
    let raw = input.value.replace(/\D/g, "");
    if (raw.startsWith("8")) raw = "7" + raw.slice(1);
    if (!raw.startsWith("7") && raw.length > 0) raw = "7" + raw;
    raw = raw.slice(0, 11);
    let masked = "+7";
    if (raw.length > 1) masked += " (" + raw.slice(1, 4);
    if (raw.length >= 4) masked += ") " + raw.slice(4, 7);
    if (raw.length >= 7) masked += "-" + raw.slice(7, 9);
    if (raw.length >= 9) masked += "-" + raw.slice(9, 11);
    input.value = masked;
  }

  function isPhoneComplete(value) {
    return String(value).replace(/\D/g, "").length === 11;
  }

  function pluralizeFiles(count) {
    const mod10 = count % 10;
    const mod100 = count % 100;
    if (mod10 === 1 && mod100 !== 11) return `${count} файл`;
    if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) {
      return `${count} файла`;
    }
    return `${count} файлов`;
  }

  function updateAttachmentsUi() {
    if (!attachmentsBar || !attachmentsLabel) return;
    const count = pendingAttachments.length;
    if (!count) {
      attachmentsBar.hidden = true;
      attachmentsLabel.textContent = "";
      return;
    }
    attachmentsBar.hidden = false;
    attachmentsLabel.textContent = `прикреплено: ${pluralizeFiles(count)}`;
  }

  function clearAttachments() {
    pendingAttachments = [];
    if (fileInput) fileInput.value = "";
    updateAttachmentsUi();
  }

  function addAttachments(fileList) {
    const incoming = Array.from(fileList || []);
    if (!incoming.length) return;
    const room = MAX_ATTACHMENTS - pendingAttachments.length;
    if (room <= 0) return;
    pendingAttachments = pendingAttachments.concat(incoming.slice(0, room));
    updateAttachmentsUi();
  }

  /* ─── Lead overlay form ──────────────────────────── */
  function getLeadFormInputs() {
    if (!leadForm) return null;
    return {
      name: leadForm.querySelector("#prmn-lead-name"),
      phone: leadForm.querySelector("#prmn-lead-phone"),
      message: leadForm.querySelector("#prmn-lead-message"),
      consent: leadForm.querySelector("#prmn-lead-consent"),
      error: leadForm.querySelector("#prmn-lead-error"),
      submit: leadForm.querySelector("#prmn-lead-submit"),
    };
  }

  function syncFormFieldState(input) {
    const field = input.closest("[data-prmn-field]");
    if (!field) return;
    field.classList.toggle("filled", input.value.trim().length > 0);
  }

  function bindLeadFormField(input) {
    const field = input.closest("[data-prmn-field]");
    if (!field) return;

    const setFocus = (on) => field.classList.toggle("focus", on);

    input.addEventListener("focus", () => setFocus(true));
    input.addEventListener("blur", () => setFocus(false));
    input.addEventListener("input", () => {
      syncFormFieldState(input);
      const f = getLeadFormInputs();
      if (f) f.error.classList.remove("prmn-visible");
      updateLeadSubmitState();
    });

    syncFormFieldState(input);
  }

  function isLeadFormValid() {
    const f = getLeadFormInputs();
    if (!f) return false;
    const nameOk = f.name.value.trim().length >= 2;
    const phoneOk = isPhoneComplete(f.phone.value);
    return nameOk && phoneOk && f.consent.checked;
  }

  function updateLeadSubmitState() {
    const f = getLeadFormInputs();
    if (!f) return;
    f.submit.disabled = !isLeadFormValid();
  }

  function bindLeadFormEvents() {
    const f = getLeadFormInputs();
    if (!f) return;

    bindLeadFormField(f.name);
    bindLeadFormField(f.phone);
    bindLeadFormField(f.message);

    f.phone.addEventListener("input", () => applyPhoneMask(f.phone));
    f.phone.addEventListener("focus", () => {
      if (!f.phone.value.replace(/\D/g, "")) {
        f.phone.value = "7";
        applyPhoneMask(f.phone);
      }
    });
    f.phone.addEventListener("blur", () => {
      if (f.phone.value.replace(/\D/g, "") === "7") f.phone.value = "";
      syncFormFieldState(f.phone);
      updateLeadSubmitState();
    });

    f.consent.addEventListener("change", updateLeadSubmitState);
    leadForm.addEventListener("submit", (e) => {
      e.preventDefault();
      submitLeadForm();
    });

    document.querySelectorAll(".prmn-js-lead-close").forEach((btn) => {
      btn.addEventListener("click", closeLeadOverlay);
    });

    updateLeadSubmitState();
  }

  function showFormReopenButton() {
    if (isLeadSubmitted() || !leadFormAvailable) return;
    if (formReopenEl?.isConnected) return;

    const row = document.createElement("div");
    row.className = "prmn-msg-row prmn-msg-row-assistant";

    const btn = document.createElement("button");
    btn.type = "button";
    btn.className = "prmn-form-reopen btn btn--accent";
    btn.textContent = REOPEN_FORM_BTN_TEXT;
    btn.addEventListener("click", showLeadOverlay);

    row.appendChild(btn);
    messages.appendChild(row);
    formReopenEl = row;
    scrollMessages();
  }

  function removeFormReopenButton() {
    if (formReopenEl?.parentElement) {
      formReopenEl.parentElement.removeChild(formReopenEl);
    }
    formReopenEl = null;
  }

  function showLeadOverlay() {
    if (isLeadSubmitted() || !leadFormAvailable || !leadOverlay) return;
    localStorage.removeItem(LS_KEY_LEAD_FORM_CLOSED);
    removeFormReopenButton();
    leadOverlay.hidden = false;
    leadOverlay.classList.remove("prmn-lead-overlay--visible");
    void leadOverlay.offsetWidth;
    requestAnimationFrame(() => {
      leadOverlay.classList.add("prmn-lead-overlay--visible");
    });
    scrollMessages();
  }

  function finishLeadOverlayClose() {
    if (!leadOverlay) return;
    leadOverlay.classList.remove("prmn-lead-overlay--visible");
    leadOverlay.hidden = true;
  }

  function closeLeadOverlay() {
    if (!leadOverlay || leadOverlay.hidden) return;
    if (!leadOverlay.classList.contains("prmn-lead-overlay--visible")) {
      finishLeadOverlayClose();
      if (leadFormAvailable && !isLeadSubmitted()) {
        markLeadFormClosed();
        showFormReopenButton();
      }
      return;
    }

    leadOverlay.classList.remove("prmn-lead-overlay--visible");

    let done = false;
    const finish = () => {
      if (done) return;
      done = true;
      leadOverlay.removeEventListener("transitionend", onEnd);
      finishLeadOverlayClose();
      if (leadFormAvailable && !isLeadSubmitted()) {
        markLeadFormClosed();
        showFormReopenButton();
      }
    };

    const onEnd = (e) => {
      if (e.target !== leadOverlay) return;
      if (e.propertyName === "opacity" || e.propertyName === "visibility")
        finish();
    };

    leadOverlay.addEventListener("transitionend", onEnd);
    setTimeout(finish, OVERLAY_ANIM_MS + 80);
  }

  function hideLeadOverlay() {
    if (leadOverlay) {
      leadOverlay.classList.remove("prmn-lead-overlay--visible");
      leadOverlay.hidden = true;
    }
    clearLeadFormState();
    removeFormReopenButton();
    if (formShowTimer) {
      clearTimeout(formShowTimer);
      formShowTimer = null;
    }
  }

  function scheduleLeadOverlay() {
    if (isLeadSubmitted() || !pendingShowForm) return;
    markLeadFormPending();
    if (formShowTimer) clearTimeout(formShowTimer);
    formShowTimer = setTimeout(() => {
      formShowTimer = null;
      pendingShowForm = false;
      showLeadOverlay();
    }, FORM_SHOW_DELAY_MS);
  }

  /* ─── Messages UI ────────────────────────────────── */
  function renderMessageBubble(role, text, reaction, filesCount) {
    const row = document.createElement("div");
    row.className = `prmn-msg-row prmn-msg-row-${role}`;

    const bubble = document.createElement("div");
    bubble.className = `prmn-msg prmn-msg-${role}`;
    if (text) bubble.textContent = text;
    row.appendChild(bubble);

    if (filesCount > 0) {
      const filesNote = document.createElement("div");
      filesNote.className = "prmn-msg-files";
      filesNote.textContent = `📎 ${pluralizeFiles(filesCount)}`;
      bubble.appendChild(filesNote);
    }

    if (role === "user") {
      lastUserBubbleRow = row;
      if (reaction) {
        const badge = document.createElement("span");
        badge.className = "prmn-msg-reaction";
        badge.textContent = reaction;
        row.appendChild(badge);
      }
    }

    messages.appendChild(row);
    scrollMessages();
    return bubble;
  }

  function applyReactionToLastUserMessage(emoji) {
    if (!emoji || !lastUserBubbleRow) return;
    let badge = lastUserBubbleRow.querySelector(".prmn-msg-reaction");
    if (!badge) {
      badge = document.createElement("span");
      badge.className = "prmn-msg-reaction";
      lastUserBubbleRow.appendChild(badge);
    }
    badge.textContent = emoji;

    for (let i = chatMessages.length - 1; i >= 0; i--) {
      if (chatMessages[i].role === "user") {
        chatMessages[i].reaction = emoji;
        writeHistoryCache(chatMessages);
        break;
      }
    }
    scrollMessages();
  }

  function showWelcomeMessage() {
    if (welcomeShown || chatMessages.length > 0) return;
    welcomeShown = true;
    renderMessageBubble("assistant", WELCOME_TEXT);
  }

  function clearMessagesDom() {
    messages.innerHTML = "";
    formReopenEl = null;
    lastUserBubbleRow = null;
    welcomeShown = false;
  }

  /* ─── History cache + API ────────────────────────── */
  function readHistoryCache() {
    try {
      const raw = sessionStorage.getItem(LS_KEY_HISTORY);
      if (!raw) return [];
      const data = JSON.parse(raw);
      if (data.session_id !== SESSION_ID || !Array.isArray(data.messages))
        return [];
      return normalizeHistoryMessages(data.messages);
    } catch {
      return [];
    }
  }

  function writeHistoryCache(messagesList) {
    sessionStorage.setItem(
      LS_KEY_HISTORY,
      JSON.stringify({
        session_id: SESSION_ID,
        messages: messagesList,
        updatedAt: Date.now(),
      }),
    );
  }

  function normalizeHistoryMessages(list) {
    return list
      .filter((m) => m && (m.role === "user" || m.role === "assistant"))
      .map((m) => ({
        role: m.role,
        content: String(m.content ?? m.text ?? "").trim(),
        ts: m.ts || null,
        reaction: m.reaction ? String(m.reaction) : "",
      }))
      .filter((m) => m.content);
  }

  function historySignature(messagesList) {
    return messagesList
      .map((m) => `${m.role}:${m.content}:${m.ts || ""}:${m.reaction || ""}`)
      .join("\n");
  }

  function mapHistoryReactions(messagesList) {
    const msgs = messagesList.map((m) => ({ ...m }));
    for (let i = 0; i < msgs.length; i++) {
      if (msgs[i].role === "assistant" && msgs[i].reaction) {
        for (let j = i - 1; j >= 0; j--) {
          if (msgs[j].role === "user") {
            msgs[j].reaction = msgs[i].reaction;
            break;
          }
        }
        msgs[i].reaction = "";
      }
    }
    return msgs;
  }

  function renderHistory(messagesList) {
    chatMessages = mapHistoryReactions(messagesList);
    clearMessagesDom();

    if (!chatMessages.length) {
      showWelcomeMessage();
      restoreLeadFormUi();
      return;
    }

    dialogStarted = true;
    welcomeShown = true;

    for (const msg of chatMessages) {
      renderMessageBubble(
        msg.role,
        msg.content,
        msg.role === "user" ? msg.reaction : undefined,
      );
    }

    writeHistoryCache(chatMessages);
    restoreLeadFormUi();
  }

  function pushChatMessage(role, content, filesCount = 0) {
    const text = String(content ?? "").trim();
    if (!text && !filesCount) return null;
    welcomeShown = true;
    dialogStarted = true;
    if (role === "user") clearAllAutoTimers();
    chatMessages.push({
      role,
      content: text,
      reaction: "",
      filesCount: filesCount || 0,
    });
    writeHistoryCache(chatMessages);
    return renderMessageBubble(role, text, undefined, filesCount);
  }

  function persistAssistantReply(text) {
    const content = String(text ?? "").trim();
    if (!content) return;
    chatMessages.push({ role: "assistant", content, reaction: "" });
    writeHistoryCache(chatMessages);
  }

  async function fetchChatHistory() {
    const url = `${API_BASE}/chat/history?session_id=${encodeURIComponent(SESSION_ID)}&limit=${HISTORY_LIMIT}`;
    const res = await fetch(url);
    if (res.status === 429) return { error: "rate_limit" };
    if (res.status === 422) return { error: "invalid_session" };
    if (!res.ok) return { error: "server" };
    const data = await res.json();
    return { messages: normalizeHistoryMessages(data.messages || []) };
  }

  async function syncHistoryFromApi() {
    if (historySyncing) return;
    historySyncing = true;

    try {
      const result = await fetchChatHistory();
      if (result.error === "rate_limit") {
        renderMessageBubble(
          "assistant",
          "Слишком много запросов, подождите немного.",
        );
        return;
      }
      if (result.error) return;

      // Гонка первого сообщения / активный диалог: если посетитель уже пишет или получает
      // ответ в этой загрузке страницы, фоновый sync истории не должен перерисовывать или
      // стирать диалог (иначе первое сообщение новой сессии исчезает — гонка с /chat/history).
      if (isStreaming || hasUserMessages()) return;

      const apiMessages = result.messages;

      if (apiMessages.length > 0) {
        dialogStarted = true;
        if (historySignature(apiMessages) !== historySignature(chatMessages)) {
          renderHistory(apiMessages);
        } else {
          writeHistoryCache(apiMessages);
          restoreLeadFormUi();
        }
        return;
      }

      if (chatMessages.length) {
        chatMessages = [];
        clearMessagesDom();
      }
      if (!welcomeShown) showWelcomeMessage();
    } catch {
      /* оставляем кэш */
    } finally {
      historySyncing = false;
    }
  }

  function restoreHistoryFromCache() {
    const cached = readHistoryCache();
    if (cached.length) {
      renderHistory(cached);
      return;
    }
    clearMessagesDom();
    showWelcomeMessage();
    restoreLeadFormUi();
  }

  /* ─── Health / offline ───────────────────────────── */
  async function checkHealth() {
    try {
      const res = await fetch(`${API_BASE}/health`);
      if (!res.ok) throw new Error("bad_status");
      const data = await res.json();
      if (data.status !== "ok") throw new Error("not_ok");
      isOffline = false;
      offlineBanner.hidden = true;
      chatInput.disabled = false;
      sendBtn.disabled = false;
      if (isLeadSubmitted()) hideLeadOverlay();
    } catch {
      isOffline = true;
      offlineBanner.hidden = false;
      chatInput.disabled = true;
      sendBtn.disabled = true;
    }
  }

  /* ─── SSE stream ─────────────────────────────────── */
  function parseSseBlock(block) {
    const lines = block.split("\n");
    let eventName = "";
    let dataStr = "";
    for (const line of lines) {
      if (line.startsWith("event:")) eventName = line.slice(6).trim();
      if (line.startsWith("data:")) dataStr = line.slice(5).trim();
    }
    return { eventName, dataStr };
  }

  async function streamAssistant(userText, files = []) {
    const bubble = renderMessageBubble("assistant", "");
    pendingShowForm = false;
    isStreaming = true;

    try {
      const page = String(window.location.href).slice(0, 500);
      let res;

      if (files.length) {
        const formData = new FormData();
        formData.append("session_id", SESSION_ID);
        formData.append("message", userText);
        formData.append("page", page);
        files.forEach((file) => formData.append("files", file));
        res = await fetch(`${API_BASE}/chat`, {
          method: "POST",
          body: formData,
        });
      } else {
        res = await fetch(`${API_BASE}/chat`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            session_id: SESSION_ID,
            message: userText,
            page,
          }),
        });
      }

      if (res.status === 429) {
        bubble.textContent = "Слишком много сообщений, подождите немного.";
        persistAssistantReply(bubble.textContent);
        return;
      }
      if (!res.ok) throw new Error("server_error");

      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = "";

      while (true) {
        const { value, done } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const blocks = buffer.split("\n\n");
        buffer = blocks.pop();

        for (const block of blocks) {
          if (!block.trim()) continue;
          const { eventName, dataStr } = parseSseBlock(block);

          if (eventName === "done" || block.startsWith("event: done")) {
            persistAssistantReply(bubble.textContent);
            if (pendingShowForm && !isLeadSubmitted()) scheduleLeadOverlay();
            return;
          }

          if (eventName === "show_form") {
            pendingShowForm = true;
            continue;
          }

          if (eventName === "reaction" && dataStr) {
            try {
              const payload = JSON.parse(dataStr);
              if (payload.emoji) applyReactionToLastUserMessage(payload.emoji);
            } catch {
              /* ignore */
            }
            continue;
          }

          if (dataStr) {
            try {
              const payload = JSON.parse(dataStr);
              if (payload.text) {
                bubble.textContent += payload.text;
                scrollMessages();
              }
            } catch {
              /* ignore */
            }
          }
        }
      }

      persistAssistantReply(bubble.textContent);
      if (pendingShowForm && !isLeadSubmitted()) scheduleLeadOverlay();
    } catch {
      bubble.textContent = "Сервис временно недоступен. Попробуйте позже.";
      persistAssistantReply(bubble.textContent);
      isOffline = true;
      offlineBanner.hidden = false;
      chatInput.disabled = true;
      sendBtn.disabled = true;
    } finally {
      isStreaming = false;
    }
  }

  /* ─── Lead submit ────────────────────────────────── */
  async function submitLeadForm() {
    const f = getLeadFormInputs();
    if (!f || !isLeadFormValid()) return;

    f.error.classList.remove("prmn-visible");
    f.error.textContent = "";
    f.submit.disabled = true;

    const name = f.name.value.trim();
    const phone = f.phone.value.trim();
    const task_description = f.message.value.trim();

    try {
      const res = await fetch(`${API_BASE}/lead`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          session_id: SESSION_ID,
          name,
          phone,
          telegram_username: "",
          email: "",
          task_description,
        }),
      });

      if (res.status === 422) {
        f.error.textContent = "Некорректный номер телефона";
        f.error.classList.add("prmn-visible");
        return;
      }
      if (!res.ok) throw new Error("server_error");

      markLeadSubmitted();
      hideLeadOverlay();
      chatMessages.push({
        role: "assistant",
        content: LEAD_CONFIRM_TEXT,
        reaction: "",
      });
      writeHistoryCache(chatMessages);
      renderMessageBubble("assistant", LEAD_CONFIRM_TEXT);
      chatInput.focus();
    } catch {
      f.error.textContent = "Ошибка соединения. Попробуйте ещё раз.";
      f.error.classList.add("prmn-visible");
    } finally {
      updateLeadSubmitState();
    }
  }

  /* ─── Chat send ──────────────────────────────────── */
  async function sendMessage() {
    const text = chatInput.value.trim();
    const files = pendingAttachments.slice();
    if ((!text && !files.length) || isStreaming) return;

    chatInput.value = "";
    const filesCount = files.length;
    clearAttachments();
    sendBtn.disabled = true;

    pushChatMessage("user", text, filesCount);
    await streamAssistant(text, files);

    sendBtn.disabled = false;
    chatInput.focus();
  }

  /* ─── Auto open ──────────────────────────────────── */
  function hasUserMessages() {
    return chatMessages.some((m) => m.role === "user");
  }

  function clearAutoOpenTimer() {
    if (autoOpenTimer) {
      clearTimeout(autoOpenTimer);
      autoOpenTimer = null;
    }
  }

  function clearAutoReopenTimer() {
    if (autoReopenTimer) {
      clearTimeout(autoReopenTimer);
      autoReopenTimer = null;
    }
  }

  function clearAllAutoTimers() {
    clearAutoOpenTimer();
    clearAutoReopenTimer();
  }

  function tryAutoOpenWidget() {
    if (widget.classList.contains("prmn-active")) return;
    if (hasUserMessages()) return;
    openWidget();
  }

  function scheduleAutoOpen(delay = AUTO_OPEN_DELAY_MS) {
    clearAutoOpenTimer();
    autoOpenTimer = setTimeout(() => {
      autoOpenTimer = null;
      tryAutoOpenWidget();
    }, delay);
  }

  function scheduleAutoReopen() {
    clearAutoReopenTimer();
    if (hasUserMessages()) return;
    autoReopenTimer = setTimeout(() => {
      autoReopenTimer = null;
      tryAutoOpenWidget();
    }, AUTO_REOPEN_DELAY_MS);
  }

  function setupAutoOpen() {
    scheduleAutoOpen(AUTO_OPEN_DELAY_MS);
  }

  function clearLauncherHintTimer() {
    if (launcherHintTimer) {
      clearTimeout(launcherHintTimer);
      launcherHintTimer = null;
    }
  }

  function showLauncherHintIntro() {
    if (
      !launcher ||
      !launcherLabel ||
      launcherHintDismissed ||
      widget.classList.contains("prmn-active")
    ) {
      return;
    }

    launcher.classList.add("prmn-launcher-wrap--hint-active");
    launcherLabel.classList.add("prmn-launcher-label--intro");
  }

  function dismissLauncherHintIntro() {
    if (launcherHintDismissed) return;
    launcherHintDismissed = true;
    clearLauncherHintTimer();

    launcher?.classList.remove("prmn-launcher-wrap--hint-active");
    launcher?.classList.add("prmn-launcher-wrap--hint-done");
    launcherLabel?.classList.remove("prmn-launcher-label--intro");

    if (!launcher?.matches(":hover")) {
      launcherLabel?.classList.remove("prmn-launcher-label--visible");
    }
  }

  function bindLauncherHintEvents() {
    if (!launcher || launcher.dataset.hintBound) return;
    launcher.dataset.hintBound = "1";

    launcher.addEventListener("mouseenter", dismissLauncherHintIntro);
    launcher.addEventListener("focusin", dismissLauncherHintIntro);
    launcher.addEventListener("touchstart", () => dismissLauncherHintIntro(), {
      passive: true,
    });

    launcher.addEventListener("mouseleave", () => {
      if (!launcherHintDismissed) return;
      if (!launcher.matches(":hover")) {
        launcherLabel.classList.remove("prmn-launcher-label--visible");
      }
    });
  }

  function setupLauncherHintIntro() {
    clearLauncherHintTimer();
    launcherHintTimer = setTimeout(
      showLauncherHintIntro,
      LAUNCHER_HINT_DELAY_MS,
    );
  }

  function applyOpenWidgetViewport() {
    if (
      window.matchMedia("(max-width: 600px)").matches &&
      window.visualViewport
    ) {
      const vv = window.visualViewport;
      widget.style.top = `${vv.offsetTop}px`;
      widget.style.height = `${vv.height}px`;
      widget.style.maxHeight = `${vv.height}px`;
    }
  }

  function animateWidgetOpen() {
    launcher.classList.add("prmn-launcher-wrap--hidden");
    widget.setAttribute("aria-hidden", "false");
    widget.classList.remove("prmn-active");
    void widget.offsetWidth;
    requestAnimationFrame(() => {
      widget.classList.add("prmn-active");
    });
  }

  function animateWidgetClose() {
    widget.classList.remove("prmn-active");
    widget.setAttribute("aria-hidden", "true");

    let done = false;
    const finish = () => {
      if (done) return;
      done = true;
      widget.removeEventListener("transitionend", onEnd);
      widget.style.top = "";
      widget.style.height = "";
      widget.style.maxHeight = "";
      launcher.classList.remove("prmn-launcher-wrap--hidden");
    };

    const onEnd = (e) => {
      if (e.target !== widget) return;
      if (e.propertyName === "transform" || e.propertyName === "opacity")
        finish();
    };

    widget.addEventListener("transitionend", onEnd);
    setTimeout(finish, WIDGET_ANIM_MS + 80);
  }

  /* ─── Open / close ───────────────────────────────── */
  function openWidget() {
    clearAutoReopenTimer();
    dismissLauncherHintIntro();

    animateWidgetOpen();

    if (!chatMessages.length && !welcomeShown) {
      restoreHistoryFromCache();
    }

    syncHistoryFromApi();
    checkHealth();
    applyOpenWidgetViewport();
    chatInput.focus();
    scrollMessages();
  }

  async function openWidgetWithUserMessage(text, options = {}) {
    const message = String(text ?? "").trim();
    if (!message) {
      openWidget();
      return;
    }

    clearAutoReopenTimer();
    dismissLauncherHintIntro();
    clearAllAutoTimers();

    animateWidgetOpen();

    if (!chatMessages.length && !welcomeShown) {
      restoreHistoryFromCache();
    }

    await syncHistoryFromApi();
    pushChatMessage("user", message);

    if (options.markLead) {
      markLeadSubmitted();
      hideLeadOverlay();
    }

    checkHealth();
    applyOpenWidgetViewport();
    scrollMessages();
  }

  function closeWidget() {
    animateWidgetClose();

    if (!hasUserMessages()) {
      scheduleAutoReopen();
    }
  }

  /* ─── Events ─────────────────────────────────────── */
  function bindEvents() {
    const launcherButton = document.getElementById("prmn-open");
    launcherButton.addEventListener("click", openWidget);

    if (launcherLabel) {
      launcherLabel.addEventListener("click", openWidget);
      launcherLabel.addEventListener("keydown", (e) => {
        if (e.key === "Enter" || e.key === " ") {
          e.preventDefault();
          openWidget();
        }
      });
    }

    document.querySelectorAll(".prmn-js-close").forEach((btn) => {
      btn.addEventListener("click", closeWidget);
    });

    sendBtn.addEventListener("click", sendMessage);
    chatInput.addEventListener("keydown", (e) => {
      if (e.key === "Enter" && !e.shiftKey) {
        e.preventDefault();
        sendMessage();
      }
    });

    attachBtn.addEventListener("click", () => fileInput.click());
    fileInput.addEventListener("change", () => {
      addAttachments(fileInput.files);
      fileInput.value = "";
    });
    attachmentsClearBtn.addEventListener("click", clearAttachments);
  }

  /* ─── Init ───────────────────────────────────────── */
  function init() {
    mount();
    bindRefs();
    bindEvents();
    bindLeadFormEvents();
    bindMobileViewport();
    setupAutoOpen();
    bindLauncherHintEvents();
    setupLauncherHintIntro();
    restoreHistoryFromCache();
    checkHealth();
  }

  window.PrmnChat = {
    open(message, options) {
      if (!widget) {
        document.getElementById("prmn-open")?.click();
        return;
      }
      clearAllAutoTimers();
      if (message) {
        openWidgetWithUserMessage(message, options);
      } else {
        openWidget();
      }
    },
  };

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();