/* =========================================================
   Celena Commerce theme — interactive behaviors.
   Vanilla JS, не зависит от jQuery. Работает после DOMContentLoaded.
   Модули:
     - HeroSlider           автокарусель главного баннера
     - HorizontalCarousel   стрелки прокрутки секции «Кращі»
     - CatalogDropdown      разворачивание панели каталога в шапке
     - ProductPricing       расчёт скидки/бонусов/частин из data-price
     - ProductHoverEdge     edge-aware позиция hover-панели
     - ViewedProducts       чтение localStorage «celena_shop_viewed»
     - WeekendCountdown     таймер «Товара выходных»
     - CartCounter          синхронизация бейджа кошика с shop.js
   ========================================================= */

(function () {
  'use strict';

  function ready(fn) {
    if (document.readyState !== 'loading') fn();
    else document.addEventListener('DOMContentLoaded', fn, { once: true });
  }

  /* ─────────────  HeroSlider  ───────────── */
  function initHeroSlider() {
    var hero = document.querySelector('[data-cm-hero]');
    if (!hero) return;

    var slides = hero.querySelectorAll('[data-cm-hero-slide]');
    var dots = hero.querySelectorAll('[data-cm-hero-dot]');
    var prev = hero.querySelector('[data-cm-hero-prev]');
    var next = hero.querySelector('[data-cm-hero-next]');
    if (!slides.length) return;

    var index = 0;
    var paused = false;
    var timer = null;
    var INTERVAL = 6000;

    function show(i) {
      index = (i + slides.length) % slides.length;
      slides.forEach(function (s, n) { s.classList.toggle('is-active', n === index); });
      dots.forEach(function (d, n) { d.classList.toggle('is-active', n === index); });
    }

    function start() {
      stop();
      timer = setInterval(function () {
        if (!paused) show(index + 1);
      }, INTERVAL);
    }
    function stop() { if (timer) { clearInterval(timer); timer = null; } }

    if (prev) prev.addEventListener('click', function () { show(index - 1); start(); });
    if (next) next.addEventListener('click', function () { show(index + 1); start(); });
    dots.forEach(function (d, n) {
      d.addEventListener('click', function () { show(n); start(); });
    });

    hero.addEventListener('mouseenter', function () { paused = true; });
    hero.addEventListener('mouseleave', function () { paused = false; });
    hero.addEventListener('focusin',  function () { paused = true; });
    hero.addEventListener('focusout', function () { paused = false; });

    show(0);
    start();
  }

  /* ─────────────  HorizontalCarousel (универсальный)  ───────────── */
  function bindHorizontalScroll(track, prev, next, stepFactor) {
    if (!track) return;
    var factor = stepFactor || 0.85;
    function step() {
      return Math.max(track.clientWidth * factor, 240);
    }
    function update() {
      var max = track.scrollWidth - track.clientWidth - 1;
      if (prev) prev.toggleAttribute('hidden', track.scrollLeft <= 0);
      if (next) next.toggleAttribute('hidden', track.scrollLeft >= max);
    }
    if (prev) prev.addEventListener('click', function () { track.scrollBy({ left: -step(), behavior: 'smooth' }); });
    if (next) next.addEventListener('click', function () { track.scrollBy({ left:  step(), behavior: 'smooth' }); });
    track.addEventListener('scroll', update, { passive: true });
    window.addEventListener('resize', update);
    update();
  }

  function initCarousels() {
    document.querySelectorAll('[data-cm-carousel]').forEach(function (root) {
      bindHorizontalScroll(
        root.querySelector('[data-cm-carousel-track]'),
        root.querySelector('[data-cm-carousel-prev]'),
        root.querySelector('[data-cm-carousel-next]')
      );
    });
  }

  /* ─────────────  CategoryCarousel — горизонтальный скролл плиток  ───────────── */
  function initCategoryCarousel() {
    var root = document.querySelector('[data-cm-cats-root]');
    if (!root) return;
    bindHorizontalScroll(
      root.querySelector('[data-cm-cats-track]'),
      root.querySelector('[data-cm-cats-prev]'),
      root.querySelector('[data-cm-cats-next]'),
      0.7
    );
  }

  /* ─────────────  Catalog dropdown  ───────────── */
  function initCatalog() {
    var btn = document.querySelector('[data-cm-catalog-toggle]');
    var panel = document.querySelector('[data-cm-catalog-panel]');
    if (!btn || !panel) return;

    function setOpen(open) {
      btn.setAttribute('aria-expanded', open ? 'true' : 'false');
      panel.toggleAttribute('hidden', !open);
    }
    btn.addEventListener('click', function (e) {
      e.stopPropagation();
      setOpen(panel.hasAttribute('hidden'));
    });
    document.addEventListener('click', function (e) {
      if (!panel.contains(e.target) && e.target !== btn) setOpen(false);
    });
    document.addEventListener('keydown', function (e) {
      if (e.key === 'Escape') setOpen(false);
    });
  }

  /* ─────────────  ProductPricing — discount/bonus/installment  ─────────────
     Шаблон не имеет арифметики, поэтому проценты, бонусы и платежи частями
     рассчитываются здесь из data-price/data-old-price. Всё клиентское — без
     зависимости на сервер. */
  function fmt(n) {
    return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
  }

  function initProductPricing() {
    document.querySelectorAll('[data-cm-product]').forEach(function (card) {
      var price = parseFloat(card.dataset.price || '0') || 0;
      var oldPrice = parseFloat(card.dataset.oldPrice || '0') || 0;

      // Скидка %
      var discount = oldPrice > price && oldPrice > 0
        ? Math.round((1 - price / oldPrice) * 100)
        : 0;

      var badge = card.querySelector('[data-cm-discount-badge]');
      if (badge) {
        if (discount > 0) badge.textContent = '−' + discount + '%';
        else badge.remove();
      }
      var dText = card.querySelector('[data-cm-discount-text]');
      if (dText && discount > 0) dText.textContent = '−' + discount + '%';

      // Частинами (12 месяцев)
      var inst = price > 0 ? Math.ceil(price / 12) : 0;
      card.querySelectorAll('[data-cm-installment]').forEach(function (el) {
        el.textContent = fmt(inst);
      });

      // Псевдо-рейтинг и отзывы (детерминированно по id)
      var id = parseInt(card.dataset.id || '0', 10) || 0;
      var rating = (3.8 + ((id * 7) % 12) / 10).toFixed(1);
      card.querySelectorAll('[data-cm-rating-value]').forEach(function (el) {
        el.textContent = rating;
      });
      card.querySelectorAll('[data-cm-reviews-value]').forEach(function (el) {
        el.textContent = (10 + (id * 17) % 480);
      });
    });
  }

  /* ─────────────  ProductVariations — selectы в hover-панели  ─────────────
     Если у товара есть options + variations (JSON в data-атрибутах),
     рисуем селект для каждой опции (Цвет / Размер / ...). При выборе:
       - находим matching variation в массиве
       - обновляем цену в карточке и в hover-панели
       - обновляем data-pprice/data-psku/data-pcover/data-variation-id/-label
         на кнопках «В кошик» (мини и в hover-панели), чтобы shop.js Cart.add
         сохранил вариацию в localStorage с правильными ценой и идентификатором.  */
  function initProductVariations() {
    document.querySelectorAll('[data-cm-product]').forEach(function (card) {
      var optsRaw = card.getAttribute('data-product-options') || '';
      var varsRaw = card.getAttribute('data-product-variations') || '';
      if (!optsRaw || !varsRaw) return;

      var options    = safeJsonArray(optsRaw);
      var variations = safeJsonArray(varsRaw);
      if (!options.length || !variations.length) return;

      var holders = card.querySelectorAll('[data-cm-variation-options], [data-cm-variation-options-mini]');
      if (!holders.length) return;

      var basePrice = parseFloat(card.getAttribute('data-price')) || 0;
      var baseCover = card.getAttribute('data-cover') || '';

      var priceMini   = card.querySelector('[data-cm-price-now]');
      var priceBig    = card.querySelector('[data-cm-price-now-big]');
      var priceOldEl  = card.querySelector('[data-cm-price-old]');
      var outBox      = card.querySelector('[data-cm-out]');
      var cartBtns    = card.querySelectorAll('[data-add-to-cart]');
      var hoverImg    = card.querySelector('.cm-product-hover__image img');
      var mainImg     = card.querySelector('.cm-product-image__img');

      var selected = options.map(function (o) {
        return (o && o.values && o.values[0]) ? o.values[0] : '';
      });

      function findVariation() {
        for (var i = 0; i < variations.length; i++) {
          var v = variations[i];
          if (!v || !v.values || v.values.length !== selected.length) continue;
          var match = true;
          for (var j = 0; j < selected.length; j++) {
            if (String(v.values[j]) !== String(selected[j])) { match = false; break; }
          }
          if (match) return v;
        }
        return null;
      }

      function renderSelectors() {
        // Рендерим селекторы в каждый контейнер (mini в карточке + полный в hover-панели).
        // При изменении любого из них — синхронизируем все остальные через querySelectorAll.
        holders.forEach(function (holder) {
          holder.innerHTML = '';
          options.forEach(function (opt, idx) {
            if (!opt || !opt.name) return;
            var wrap = document.createElement('label');
            wrap.className = 'cm-variation-option';
            var name = document.createElement('span');
            name.className = 'cm-variation-option__name';
            name.textContent = opt.name + ':';
            wrap.appendChild(name);
            var sel = document.createElement('select');
            sel.className = 'cm-variation-option__select';
            sel.dataset.optIndex = String(idx);
            (opt.values || []).forEach(function (val) {
              var o = document.createElement('option');
              o.value = String(val);
              o.textContent = String(val);
              if (String(val) === String(selected[idx])) o.selected = true;
              sel.appendChild(o);
            });
            sel.addEventListener('click', function (e) { e.stopPropagation(); });
            sel.addEventListener('change', function (e) {
              e.stopPropagation();
              selected[idx] = sel.value;
              syncSelectors(idx, sel.value);
              applyVariation();
            });
            wrap.appendChild(sel);
            holder.appendChild(wrap);
          });
        });
      }

      function syncSelectors(idx, value) {
        card.querySelectorAll('.cm-variation-option__select').forEach(function (s) {
          if (parseInt(s.dataset.optIndex, 10) === idx && s.value !== value) {
            s.value = value;
          }
        });
      }

      function applyVariation() {
        var v = findVariation();
        if (!v) {
          if (outBox) { outBox.hidden = false; outBox.textContent = 'Комбінація недоступна'; }
          cartBtns.forEach(function (b) { b.disabled = true; });
          return;
        }
        var price = parseFloat(v.price) || basePrice;
        var oldP  = parseFloat(v.old_price) || 0;
        var stock = parseInt(v.stock, 10) || 0;
        var sku   = v.sku || '';
        var img   = v.image || baseCover;

        if (priceMini)  priceMini.textContent  = fmt(price) + ' ₴';
        if (priceBig)   priceBig.textContent   = fmt(price) + ' ₴';
        if (priceOldEl) {
          if (oldP > price && oldP > 0) {
            priceOldEl.textContent = fmt(oldP) + ' ₴';
            priceOldEl.style.display = '';
          } else {
            priceOldEl.style.display = 'none';
          }
        }
        if (img) {
          if (hoverImg) hoverImg.src = img;
          if (mainImg)  mainImg.src  = img;
        }

        var labelParts = options.map(function (opt, i) {
          return (opt && opt.name ? opt.name : '') + ': ' + selected[i];
        }).filter(Boolean);
        var label = labelParts.join(' · ');

        cartBtns.forEach(function (btn) {
          btn.disabled = stock <= 0;
          btn.setAttribute('data-pprice', String(price));
          btn.setAttribute('data-psku', sku);
          if (img) btn.setAttribute('data-pcover', img);
          btn.setAttribute('data-variation-id', String(v.id || ''));
          btn.setAttribute('data-variation-label', label);
        });

        if (outBox) {
          outBox.hidden = stock > 0;
          if (stock <= 0) outBox.textContent = 'Немає в наявності';
        }
        // обновим бейдж скидки и installment динамически
        var disc = oldP > price && oldP > 0 ? Math.round((1 - price / oldP) * 100) : 0;
        var dt = card.querySelector('[data-cm-discount-text]');
        if (dt && disc > 0) dt.textContent = '−' + disc + '%';
        var db = card.querySelector('[data-cm-discount-badge]');
        if (db && disc > 0) db.textContent = '−' + disc + '%';
        var inst = card.querySelector('[data-cm-installment]');
        if (inst) inst.textContent = fmt(Math.ceil(price / 12));
      }

      function safeJsonArray(s) {
        try { var p = JSON.parse(s); return Array.isArray(p) ? p : []; }
        catch (e) { return []; }
      }

      renderSelectors();
      applyVariation();
    });
  }

  /* ─────────────  ProductHover — fixed-positioned overlay panel  ─────────────
     Панель в DOM остаётся потомком карточки, поэтому :hover родителя
     срабатывает и при наведении на саму панель — закрывать вручную не нужно.
     JS только пересчитывает координаты (top/left) при mouseenter, scroll, resize. */
  function initProductHover() {
    var HOVER_WIDTH = 360;
    var GAP = 12;

    function positionHover(card, hover) {
      var rect = card.getBoundingClientRect();
      var vw = window.innerWidth;
      var vh = window.innerHeight;
      var width = Math.min(HOVER_WIDTH, vw - GAP * 2);
      hover.style.width = width + 'px';

      var cardCenter = rect.left + rect.width / 2;
      var left = cardCenter - width / 2;
      if (left < GAP) left = GAP;
      if (left + width > vw - GAP) left = vw - width - GAP;

      var top = rect.top - 12;
      // Если панель выше карточки целиком вылезает за вьюпорт — позиционируем
      // её вниз от карточки (поведение для длинных секций у нижнего края).
      var hoverHeight = hover.offsetHeight || 480;
      if (top + hoverHeight > vh - GAP) {
        top = Math.max(GAP, vh - hoverHeight - GAP);
      }
      if (top < GAP) top = GAP;

      hover.style.top = top + 'px';
      hover.style.left = left + 'px';
    }

    function isHoverDisabled(card) {
      // В режиме «Список» hover-панель не нужна — основная карточка уже
      // показывает все характеристики и большую кнопку «Купити».
      return !!card.closest('.cm-grid--catalog.is-list');
    }

    // Event delegation: работает и для карточек, добавленных динамически
    // (например, ViewedProducts из localStorage). mouseover всплывает, в отличие
    // от mouseenter — ловим вход курсора в карточку через relatedTarget.
    document.addEventListener('mouseover', function (e) {
      var card = e.target.closest('[data-cm-product]');
      if (!card) return;
      // Если курсор пришёл из той же карточки (двигаемся внутри неё) — пропускаем.
      var related = e.relatedTarget;
      if (related && card.contains(related)) return;
      if (isHoverDisabled(card)) return;
      var hover = card.querySelector('[data-cm-hover]');
      if (hover) positionHover(card, hover);
    });

    var pending = false;
    function reflowHovered() {
      if (pending) return;
      pending = true;
      requestAnimationFrame(function () {
        pending = false;
        document.querySelectorAll('[data-cm-product]:hover').forEach(function (card) {
          var hover = card.querySelector('[data-cm-hover]');
          if (hover) positionHover(card, hover);
        });
      });
    }

    window.addEventListener('scroll',  reflowHovered, { passive: true });
    window.addEventListener('resize',  reflowHovered);
    document.querySelectorAll('[data-cm-carousel-track], [data-cm-cats-track]')
      .forEach(function (t) { t.addEventListener('scroll', reflowHovered, { passive: true }); });
  }

  /* ─────────────  ViewedProducts — рендер из localStorage  ───────────── */
  function initViewed() {
    var section = document.querySelector('[data-cm-viewed-section]');
    var grid = document.querySelector('[data-cm-viewed-grid]');
    if (!section || !grid) return;

    var raw;
    try { raw = JSON.parse(localStorage.getItem('celena_shop_viewed') || '[]'); }
    catch (e) { raw = []; }
    if (!Array.isArray(raw) || !raw.length) return;

    raw.slice(0, 7).forEach(function (item) {
      if (!item || !item.id) return;
      var article = document.createElement('article');
      article.className = 'cm-product';
      article.dataset.cmProduct = '';
      article.dataset.id = String(item.id);
      article.dataset.price = String(item.price || 0);
      article.dataset.oldPrice = String(item.oldPrice || '');
      article.dataset.url = item.url || '#';
      article.innerHTML =
        '<a class="cm-product-image" href="' + (item.url || '#') + '">' +
          (item.cover ? '<img src="' + item.cover + '" alt="" loading="lazy" class="cm-product-image__img">' : '<div class="cm-product-noimg"></div>') +
        '</a>' +
        '<div class="cm-product-body">' +
          '<h3 class="cm-product-title"><a href="' + (item.url || '#') + '">' + (item.title || '') + '</a></h3>' +
          '<div class="cm-product-prices">' +
            (item.oldPriceFmt ? '<span class="cm-price-old">' + item.oldPriceFmt + ' ₴</span>' : '') +
            '<span class="cm-price-now">' + (item.priceFmt || '') + ' ₴</span>' +
          '</div>' +
        '</div>';
      grid.appendChild(article);
    });

    if (grid.children.length) section.removeAttribute('hidden');
  }

  /* ─────────────  WeekendCountdown — таймер «Товар выходных»  ───────────── */
  function initCountdown() {
    var root = document.querySelector('[data-cm-countdown]');
    if (!root) return;

    var d = root.querySelector('[data-cm-cd-d]');
    var h = root.querySelector('[data-cm-cd-h]');
    var m = root.querySelector('[data-cm-cd-m]');
    var s = root.querySelector('[data-cm-cd-s]');
    if (!d || !h || !m || !s) return;

    function nextSundayMidnight() {
      var now = new Date();
      var t = new Date(now);
      var day = t.getDay();           // 0 = вс
      var diff = (7 - day) % 7;       // дней до воскресенья
      if (diff === 0) diff = 7;       // если уже вс — берём следующее
      t.setDate(t.getDate() + diff);
      t.setHours(0, 0, 0, 0);
      return t;
    }

    var target = nextSundayMidnight().getTime();
    function pad(n) { n = Math.max(0, n); return n < 10 ? '0' + n : '' + n; }

    function tick() {
      var diff = Math.floor((target - Date.now()) / 1000);
      d.textContent = pad(Math.floor(diff / 86400));
      h.textContent = pad(Math.floor((diff % 86400) / 3600));
      m.textContent = pad(Math.floor((diff % 3600) / 60));
      s.textContent = pad(diff % 60);
    }
    tick();
    setInterval(tick, 1000);
  }

  /* ─────────────  CartCounter — синхронизация с shop.js  ───────────── */
  function initCartCounter() {
    function updateBadge(name, count) {
      document.querySelectorAll('[data-cm-counter-value="' + name + '"]').forEach(function (el) {
        if (count > 0) {
          el.textContent = String(count);
          el.removeAttribute('hidden');
        } else {
          el.setAttribute('hidden', '');
        }
      });
    }

    function syncCart() {
      var raw;
      try { raw = JSON.parse(localStorage.getItem('celena_shop_cart') || '[]'); }
      catch (e) { raw = []; }
      var n = Array.isArray(raw)
        ? raw.reduce(function (a, it) { return a + (parseInt(it && it.qty, 10) || 0); }, 0)
        : 0;
      updateBadge('cart', n);
    }
    function syncFav() {
      var raw;
      try { raw = JSON.parse(localStorage.getItem('celena_shop_favorites') || '[]'); }
      catch (e) { raw = []; }
      updateBadge('favorites', Array.isArray(raw) ? raw.length : 0);
    }
    function syncCompare() {
      var raw;
      try { raw = JSON.parse(localStorage.getItem('celena_shop_compare') || '[]'); }
      catch (e) { raw = []; }
      updateBadge('compare', Array.isArray(raw) ? raw.length : 0);
    }

    // Подсветка кнопок «обране»/«порівняння» по фактическому содержимому localStorage.
    // Главное: сердечко уже добавленного товара должно быть активным СРАЗУ при загрузке
    // страницы, а не только после клика.
    function idsOf(key) {
      var raw;
      try { raw = JSON.parse(localStorage.getItem(key) || '[]'); }
      catch (e) { raw = []; }
      return Array.isArray(raw) ? raw.map(String) : [];
    }
    function reflectActive() {
      var fav = idsOf('celena_shop_favorites');
      document.querySelectorAll('[data-cm-fav]').forEach(function (b) {
        b.classList.toggle('is-active', fav.indexOf(String(b.getAttribute('data-cm-fav'))) >= 0);
      });
      var cmp = idsOf('celena_shop_compare');
      document.querySelectorAll('[data-cm-compare]').forEach(function (b) {
        b.classList.toggle('is-active', cmp.indexOf(String(b.getAttribute('data-cm-compare'))) >= 0);
      });
    }

    syncCart(); syncFav(); syncCompare(); reflectActive();

    // Карточки, добавленные динамически (швидкий перегляд, «показати більше»),
    // тоже подсвечиваем. Реагируем только на добавление узлов с кнопкой избранного,
    // чтобы не дёргаться на каждом тике таймера/счётчика.
    var reflectRaf = 0;
    function scheduleReflect() {
      if (reflectRaf) return;
      reflectRaf = requestAnimationFrame(function () { reflectRaf = 0; reflectActive(); });
    }
    new MutationObserver(function (muts) {
      for (var i = 0; i < muts.length; i++) {
        var added = muts[i].addedNodes;
        for (var j = 0; j < added.length; j++) {
          var n = added[j];
          if (n.nodeType === 1 && (n.matches && n.matches('[data-cm-fav]') || n.querySelector && n.querySelector('[data-cm-fav]'))) {
            scheduleReflect();
            return;
          }
        }
      }
    }).observe(document.body, { childList: true, subtree: true });

    document.addEventListener('celena:cart:changed', syncCart);
    window.addEventListener('storage', function (e) {
      if (!e.key) return;
      if (e.key === 'celena_shop_cart') syncCart();
      if (e.key === 'celena_shop_favorites') { syncFav(); reflectActive(); }
      if (e.key === 'celena_shop_compare') { syncCompare(); reflectActive(); }
    });

    // Локальный фолбэк: если плагин shop не активен и у кнопок нет своего
    // обработчика, добавим минимальный inline-вариант избранного/сравнения.
    document.addEventListener('click', function (e) {
      var btn = e.target.closest('[data-cm-fav]');
      if (btn) {
        e.preventDefault();
        toggleSet('celena_shop_favorites', btn.getAttribute('data-cm-fav'));
        syncFav();
        reflectActive();
        return;
      }
      var cmp = e.target.closest('[data-cm-compare]');
      if (cmp) {
        e.preventDefault();
        toggleSet('celena_shop_compare', cmp.getAttribute('data-cm-compare'));
        syncCompare();
        reflectActive();
      }
    });

    function toggleSet(key, id) {
      var raw;
      try { raw = JSON.parse(localStorage.getItem(key) || '[]'); }
      catch (e) { raw = []; }
      if (!Array.isArray(raw)) raw = [];
      var i = raw.indexOf(id);
      if (i >= 0) raw.splice(i, 1); else raw.push(id);
      localStorage.setItem(key, JSON.stringify(raw));
    }
  }

  /* ─────────────  About read-more  ───────────── */
  function initAboutToggle() {
    var link = document.querySelector('[data-cm-about-toggle]');
    var details = document.querySelector('.cm-about__more');
    if (!link || !details) return;
    link.addEventListener('click', function (e) {
      e.preventDefault();
      details.toggleAttribute('open');
      link.textContent = details.hasAttribute('open') ? 'Згорнути' : 'Читати повністю';
    });
  }

  /* ─────────────  ShowMore — secondary expand for discounts grid  ───────────── */
  function initShowMore() {
    document.querySelectorAll('[data-cm-show-more]').forEach(function (btn) {
      btn.addEventListener('click', function () {
        var section = btn.closest('.cm-section');
        if (!section) return;
        var grid = section.querySelector('.cm-grid');
        if (!grid) return;
        grid.classList.toggle('is-expanded');
        btn.textContent = grid.classList.contains('is-expanded') ? 'Згорнути' : 'Показати більше';
      });
    });
  }

  /* ─────────────  Category filters: sort auto-submit, search, range, view-mode  ───────────── */
  function initCategorySort() {
    document.querySelectorAll('[data-cm-sort]').forEach(function (sel) {
      sel.addEventListener('change', function () {
        var form = sel.closest('form');
        if (form) form.submit();
      });
    });
  }

  function initFilterCheckboxes() {
    // Если в группе фильтра много значений — сделать поиск активным.
    document.querySelectorAll('[data-cm-filter-search]').forEach(function (input) {
      var slug = input.getAttribute('data-cm-filter-search');
      var list = document.querySelector('[data-cm-filter-list="' + slug + '"]');
      if (!list) return;
      input.addEventListener('input', function () {
        var q = input.value.toLowerCase().trim();
        list.querySelectorAll('.cm-filter-item').forEach(function (item) {
          var text = (item.querySelector('.cm-filter-item__text') || {}).textContent || '';
          var match = !q || text.toLowerCase().indexOf(q) !== -1;
          item.classList.toggle('is-hidden', !match);
        });
      });
    });

    // Восстановить состояние «checked» по URL для attr-фильтров.
    // Серверный шаблон уже проставляет [checked], это страховка для случая,
    // когда форма открыта без перезагрузки (история навигации, back/forward).
    var params = new URLSearchParams(window.location.search);
    document.querySelectorAll('[data-cm-attr-input]').forEach(function (input) {
      var name = input.name; // attr[color][]
      var match = name.match(/^attr\[([^\]]+)\]/);
      if (!match) return;
      var slug = match[1];
      var values = params.getAll('attr[' + slug + '][]');
      if (values.indexOf(input.value) !== -1) {
        input.checked = true;
      }
    });

    // Авто-submit при клике
    document.querySelectorAll('.cm-filters__form input[type="checkbox"]').forEach(function (cb) {
      cb.addEventListener('change', function () {
        // Не сабмитим сразу — даём пользователю выбрать несколько фильтров,
        // submit по кнопке «Застосувати». Если хочется live — раскомментировать:
        // cb.closest('form').submit();
      });
    });
  }

  function initPriceSlider() {
    var slider = document.querySelector('[data-cm-price-slider]');
    if (!slider) return;
    var minVal = parseFloat(slider.dataset.min || '0') || 0;
    var maxVal = parseFloat(slider.dataset.max || '0') || 1;
    if (maxVal <= minVal) return;

    var inputFrom = document.querySelector('[data-cm-price-min]');
    var inputTo   = document.querySelector('[data-cm-price-max]');
    var fill      = slider.querySelector('[data-cm-price-fill]');
    var hFrom     = slider.querySelector('[data-cm-price-handle="from"]');
    var hTo       = slider.querySelector('[data-cm-price-handle="to"]');
    if (!inputFrom || !inputTo || !fill || !hFrom || !hTo) return;

    function pct(v) { return ((v - minVal) / (maxVal - minVal)) * 100; }
    function clamp(v) { return Math.max(minVal, Math.min(maxVal, v)); }

    function paint() {
      var f = clamp(parseFloat(inputFrom.value) || minVal);
      var t = clamp(parseFloat(inputTo.value)   || maxVal);
      if (f > t) f = t;
      var pf = pct(f), pt = pct(t);
      hFrom.style.left = pf + '%';
      hTo.style.left   = pt + '%';
      fill.style.left  = pf + '%';
      fill.style.width = (pt - pf) + '%';
    }

    function drag(handle, isFrom) {
      handle.addEventListener('pointerdown', function (e) {
        e.preventDefault();
        handle.setPointerCapture(e.pointerId);
        var rect = slider.getBoundingClientRect();
        function move(ev) {
          var x = (ev.clientX - rect.left) / rect.width;
          x = Math.max(0, Math.min(1, x));
          var v = Math.round(minVal + x * (maxVal - minVal));
          if (isFrom) {
            v = Math.min(v, parseFloat(inputTo.value) || maxVal);
            inputFrom.value = v;
          } else {
            v = Math.max(v, parseFloat(inputFrom.value) || minVal);
            inputTo.value = v;
          }
          paint();
        }
        function up(ev) {
          handle.removeEventListener('pointermove', move);
          handle.removeEventListener('pointerup', up);
        }
        handle.addEventListener('pointermove', move);
        handle.addEventListener('pointerup', up);
      });
    }

    drag(hFrom, true);
    drag(hTo, false);

    [inputFrom, inputTo].forEach(function (inp) {
      inp.addEventListener('input', paint);
      inp.addEventListener('change', paint);
    });

    paint();
  }

  function initFilterMobileToggle() {
    document.querySelectorAll('[data-cm-filters-toggle]').forEach(function (btn) {
      btn.addEventListener('click', function () {
        var panel = btn.closest('[data-cm-filters]');
        if (panel) panel.classList.toggle('is-open');
      });
    });
  }

  function initActiveFilterRemove() {
    document.querySelectorAll('[data-cm-remove-filter]').forEach(function (btn) {
      btn.addEventListener('click', function () {
        var raw = btn.getAttribute('data-cm-remove-filter') || '';
        var pos = raw.indexOf('|');
        if (pos < 0) return;
        var slug = raw.substring(0, pos);
        var value = raw.substring(pos + 1);
        var url = new URL(window.location.href);
        var key = 'attr[' + slug + '][]';
        var values = url.searchParams.getAll(key);
        url.searchParams.delete(key);
        values.filter(function (v) { return v !== value; }).forEach(function (v) {
          url.searchParams.append(key, v);
        });
        url.searchParams.delete('page');
        window.location.href = url.toString();
      });
    });
  }

  function initViewToggle() {
    var toggle = document.querySelector('[data-cm-view-toggle]');
    if (!toggle) return;
    var grid = document.querySelector('[data-cm-cat-grid]');
    if (!grid) return;

    var saved = localStorage.getItem('celena_commerce_view');
    if (saved === 'list') {
      grid.classList.add('is-list');
      toggle.querySelector('[data-cm-view="grid"]').classList.remove('is-active');
      toggle.querySelector('[data-cm-view="list"]').classList.add('is-active');
    }

    toggle.querySelectorAll('[data-cm-view]').forEach(function (btn) {
      btn.addEventListener('click', function () {
        var mode = btn.getAttribute('data-cm-view');
        toggle.querySelectorAll('[data-cm-view]').forEach(function (b) {
          b.classList.toggle('is-active', b === btn);
        });
        grid.classList.toggle('is-list', mode === 'list');
        try { localStorage.setItem('celena_commerce_view', mode); } catch (e) {}
      });
    });
  }

  /* ─────────────  Product page: галерея + lightbox  ───────────── */
  function initProductGallery() {
    var gal = document.querySelector('[data-cm-gallery]');
    if (!gal) return;
    var mainImg = gal.querySelector('[data-cm-gallery-main-img]');
    var thumbs  = gal.querySelectorAll('[data-cm-gallery-thumb]');
    var openBtn = gal.querySelector('[data-cm-gallery-open]');
    if (!mainImg || !thumbs.length) return;

    var images = Array.prototype.map.call(thumbs, function (t) {
      return t.getAttribute('data-src') || '';
    }).filter(Boolean);
    var currentIndex = 0;

    function activate(idx) {
      currentIndex = Math.max(0, Math.min(images.length - 1, idx));
      thumbs.forEach(function (t, i) { t.classList.toggle('is-active', i === currentIndex); });
      mainImg.src = images[currentIndex];
    }
    thumbs.forEach(function (thumb, idx) {
      thumb.addEventListener('mouseenter', function () { activate(idx); });
      thumb.addEventListener('click', function (e) { e.preventDefault(); activate(idx); });
    });

    // Lightbox
    var lb       = document.querySelector('[data-cm-lightbox]');
    var lbImg    = lb && lb.querySelector('[data-cm-lightbox-img]');
    var lbClose  = lb && lb.querySelectorAll('[data-cm-lightbox-close]');
    var lbPrev   = lb && lb.querySelector('[data-cm-lightbox-prev]');
    var lbNext   = lb && lb.querySelector('[data-cm-lightbox-next]');
    var lbCount  = lb && lb.querySelector('[data-cm-lightbox-counter]');

    function openLb(idx) {
      if (!lb || !lbImg) return;
      currentIndex = Math.max(0, Math.min(images.length - 1, idx));
      lbImg.src = images[currentIndex];
      if (lbCount) lbCount.textContent = (currentIndex + 1) + ' / ' + images.length;
      lb.hidden = false;
      document.body.style.overflow = 'hidden';
    }
    function closeLb() {
      if (!lb) return;
      lb.hidden = true;
      document.body.style.overflow = '';
    }
    function lbStep(d) { openLb(currentIndex + d); }

    if (openBtn) {
      openBtn.addEventListener('click', function (e) {
        if (e.target.closest('.cm-product-act')) return;
        e.preventDefault();
        openLb(currentIndex);
      });
    }
    if (lbClose) lbClose.forEach(function (el) { el.addEventListener('click', closeLb); });
    if (lbPrev)  lbPrev.addEventListener('click', function () { lbStep(-1); });
    if (lbNext)  lbNext.addEventListener('click', function () { lbStep(1); });
    document.addEventListener('keydown', function (e) {
      if (!lb || lb.hidden) return;
      if (e.key === 'Escape') closeLb();
      if (e.key === 'ArrowLeft') lbStep(-1);
      if (e.key === 'ArrowRight') lbStep(1);
    });
  }

  /* ─────────────  Cart page: рендер в commerce-стиле через Cart API из shop.js
     ───────────────────────────────────────────────────────────────────────────
     Архитектура: shop.js (плагин) — единый источник истины для localStorage и
     операций с корзиной (Cart API: add/update/remove/items). Каждая тема пишет
     только свой HTML-рендер и слушает 'celena:cart:changed' для обновления.
     Здесь мы НЕ работаем с localStorage напрямую — всё через window.Celena.Shop.cart. */
  function getCartApi() {
    return (window.Celena && window.Celena.Shop && window.Celena.Shop.cart) || null;
  }

  function readCartItems() {
    var api = getCartApi();
    if (api) return api.items();
    // Fallback на чтение localStorage напрямую — только read, не write.
    try {
      var raw = localStorage.getItem('celena_shop_cart');
      var data = JSON.parse(raw || '{}');
      return Array.isArray(data.items) ? data.items : [];
    } catch (e) { return []; }
  }

  function escapeHtml(s) {
    return String(s || '').replace(/[&<>"']/g, function (c) {
      return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
    });
  }

  function initCommerceCart() {
    var list = document.querySelector('[data-cm-cart-list]');
    var summary = document.querySelector('[data-cm-cart-summary]');
    if (!list || !summary) return;

    function render() {
      var items = readCartItems();
      var pageCount = document.querySelector('[data-cm-cart-pagecount]');
      var totalQty = items.reduce(function (s, it) { return s + (parseInt(it.quantity, 10) || 0); }, 0);

      if (pageCount) pageCount.textContent = totalQty + ' товар(ів)';

      if (items.length === 0) {
        list.innerHTML = '<div class="cm-cart-empty">'
          + '<div class="cm-cart-empty__ico"></div>'
          + '<h3>Ваш кошик порожній</h3>'
          + '<p>Додайте товари з каталогу — вони з\'являться тут.</p>'
          + '<a href="/shop" class="cm-btn cm-btn--primary cm-btn--lg">До покупок</a>'
          + '</div>';
        summary.hidden = true;
        return;
      }

      var subtotal = 0;
      var html = '';
      items.forEach(function (it) {
        var qty = parseInt(it.quantity, 10) || 0;
        var price = parseFloat(it.price) || 0;
        var sub = price * qty;
        subtotal += sub;
        var pid = parseInt(it.product_id, 10) || 0;
        var vid = parseInt(it.variation_id, 10) || 0;
        var img = it.cover ? '<img src="' + escapeHtml(it.cover) + '" alt="">' : '';
        var titleHtml = '<span class="cm-cart-item__title">' + escapeHtml(it.title || ('Товар #' + pid)) + '</span>';
        var variation = it.variation_label ? '<div class="cm-cart-item__variation">' + escapeHtml(it.variation_label) + '</div>' : '';
        var sku = it.sku ? '<div class="cm-cart-item__sku">Артикул: ' + escapeHtml(it.sku) + '</div>' : '';

        html += '<article class="cm-cart-item" data-cm-cart-row="' + pid + '-' + vid + '">'
          +   '<div class="cm-cart-item__img">' + img + '</div>'
          +   '<div class="cm-cart-item__body">'
          +     titleHtml + variation + sku
          +   '</div>'
          +   '<div class="cm-cart-item__qty">'
          +     '<button type="button" data-cm-cart-dec data-pid="' + pid + '" data-vid="' + vid + '" aria-label="−"' + (qty <= 1 ? ' disabled' : '') + '>−</button>'
          +     '<input type="number" min="1" max="999" value="' + qty + '" data-cm-cart-qty data-pid="' + pid + '" data-vid="' + vid + '">'
          +     '<button type="button" data-cm-cart-inc data-pid="' + pid + '" data-vid="' + vid + '" aria-label="+">+</button>'
          +   '</div>'
          +   '<div class="cm-cart-item__price">'
          +     '<div class="cm-cart-item__price-now">' + fmt(sub) + ' ₴</div>'
          +     '<span class="cm-cart-item__price-each">' + fmt(price) + ' ₴ / шт.</span>'
          +   '</div>'
          +   '<button type="button" class="cm-cart-item__remove" data-cm-cart-remove data-pid="' + pid + '" data-vid="' + vid + '" aria-label="Видалити">'
          +     '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18M6 6l12 12"/></svg>'
          +   '</button>'
          + '</article>';
      });
      list.innerHTML = html;

      summary.hidden = false;
      var sQty = summary.querySelector('[data-cm-cart-summary-qty]');
      var sSub = summary.querySelector('[data-cm-cart-summary-subtotal]');
      var sTot = summary.querySelector('[data-cm-cart-summary-total]');
      if (sQty) sQty.textContent = totalQty;
      if (sSub) sSub.textContent = fmt(subtotal) + ' ₴';
      if (sTot) sTot.textContent = fmt(subtotal) + ' ₴';
    }

    // Делегирование событий на document — устойчиво к перерисовкам innerHTML.
    // Если Cart API недоступен — fallback на прямую работу с localStorage.
    function changeQty(pid, vid, delta, abs) {
      var items = readCartItems();
      var idx = -1;
      for (var i = 0; i < items.length; i++) {
        if ((items[i].product_id | 0) === pid && (items[i].variation_id | 0) === vid) { idx = i; break; }
      }
      if (idx < 0) return;
      var newQty = abs !== undefined ? abs : ((items[idx].quantity | 0) + delta);
      if (newQty < 1) newQty = 1; // минимум 1 — удаление только через X
      var api = getCartApi();
      if (api) {
        api.update(pid, vid, newQty);
      } else {
        items[idx].quantity = newQty;
        try { localStorage.setItem('celena_shop_cart', JSON.stringify({ items: items, v: 1 })); } catch (e) {}
        try { window.dispatchEvent(new CustomEvent('celena:cart:changed')); } catch (e) {}
      }
    }
    function removeItem(pid, vid) {
      var api = getCartApi();
      if (api) {
        api.remove(pid, vid);
      } else {
        var items = readCartItems().filter(function (it) {
          return !((it.product_id | 0) === pid && (it.variation_id | 0) === vid);
        });
        try { localStorage.setItem('celena_shop_cart', JSON.stringify({ items: items, v: 1 })); } catch (e) {}
        try { window.dispatchEvent(new CustomEvent('celena:cart:changed')); } catch (e) {}
      }
    }

    document.addEventListener('click', function (e) {
      var inc = e.target.closest && e.target.closest('[data-cm-cart-inc]');
      var dec = e.target.closest && e.target.closest('[data-cm-cart-dec]');
      var rm  = e.target.closest && e.target.closest('[data-cm-cart-remove]');
      var btn = inc || dec || rm;
      if (!btn) return;
      e.preventDefault();
      var pid = parseInt(btn.getAttribute('data-pid'), 10) || 0;
      var vid = parseInt(btn.getAttribute('data-vid'), 10) || 0;
      if (pid <= 0) return;
      if (rm) removeItem(pid, vid);
      else if (inc) changeQty(pid, vid, 1);
      else if (dec) changeQty(pid, vid, -1);
    });

    // input → real-time update с clamp в [1..999]
    document.addEventListener('input', function (e) {
      var inp = e.target.closest && e.target.closest('[data-cm-cart-qty]');
      if (!inp) return;
      var pid = parseInt(inp.getAttribute('data-pid'), 10) || 0;
      var vid = parseInt(inp.getAttribute('data-vid'), 10) || 0;
      var v = parseInt(inp.value, 10);
      if (isNaN(v)) return;
      v = Math.max(1, Math.min(999, v));
      if (String(v) !== inp.value) inp.value = String(v);
      changeQty(pid, vid, 0, v);
    });

    window.addEventListener('celena:cart:changed', render);
    window.addEventListener('storage', function (e) {
      if (e.key === 'celena_shop_cart') render();
    });

    render();
  }

  /* ─────────────  Cart mini: total в иконке + dropdown-превью + toast  ───────────── */
  function initCartMini() {
    var wrap = document.querySelector('[data-cm-cart-mini]');
    var totalEl = document.querySelector('[data-cm-cart-total]');
    var pop = document.querySelector('[data-cm-cart-mini-pop]');

    function calcTotals(items) {
      var qty = 0, sum = 0;
      items.forEach(function (it) {
        var q = parseInt(it.quantity, 10) || 0;
        var p = parseFloat(it.price) || 0;
        qty += q;
        sum += q * p;
      });
      return { qty: qty, sum: sum };
    }

    function updateTotal() {
      if (!totalEl) return;
      var t = calcTotals(readCartItems());
      if (t.qty > 0) {
        totalEl.textContent = fmt(t.sum) + ' ₴';
        totalEl.removeAttribute('hidden');
      } else {
        totalEl.setAttribute('hidden', '');
      }
    }

    function renderPop() {
      if (!pop) return;
      var items = readCartItems();
      if (items.length === 0) {
        pop.innerHTML = '<div class="cm-cart-mini__empty">'
          + '<div class="cm-cart-mini__empty-ico"></div>'
          + '<p>Кошик порожній</p>'
          + '</div>';
        return;
      }
      var t = calcTotals(items);
      var html = '<div class="cm-cart-mini__head">'
        + '<span>У кошику: <b>' + t.qty + '</b> товар(ів)</span>'
        + '</div>'
        + '<ul class="cm-cart-mini__list">';
      items.slice(0, 5).forEach(function (it) {
        var pid = parseInt(it.product_id, 10) || 0;
        var vid = parseInt(it.variation_id, 10) || 0;
        var qty = parseInt(it.quantity, 10) || 0;
        var price = parseFloat(it.price) || 0;
        var img = it.cover ? '<img src="' + escapeHtml(it.cover) + '" alt="">' : '';
        var label = it.variation_label ? '<small>' + escapeHtml(it.variation_label) + '</small>' : '';
        html += '<li class="cm-cart-mini__item">'
          +   '<div class="cm-cart-mini__img">' + img + '</div>'
          +   '<div class="cm-cart-mini__body">'
          +     '<div class="cm-cart-mini__title">' + escapeHtml(it.title || ('Товар #' + pid)) + '</div>'
          +     label
          +     '<div class="cm-cart-mini__line">' + qty + ' × ' + fmt(price) + ' ₴ = <b>' + fmt(qty * price) + ' ₴</b></div>'
          +   '</div>'
          +   '<button type="button" class="cm-cart-mini__rm" data-cm-cart-remove data-pid="' + pid + '" data-vid="' + vid + '" aria-label="Видалити">'
          +     '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18M6 6l12 12"/></svg>'
          +   '</button>'
          + '</li>';
      });
      if (items.length > 5) {
        html += '<li class="cm-cart-mini__more">…і ще ' + (items.length - 5) + '</li>';
      }
      html += '</ul>'
        + '<div class="cm-cart-mini__total">'
        +   '<span>Разом</span>'
        +   '<b>' + fmt(t.sum) + ' ₴</b>'
        + '</div>'
        + '<div class="cm-cart-mini__actions">'
        +   '<a href="/cart" class="cm-btn cm-btn--ghost cm-btn--block">До кошика</a>'
        +   '<a href="/checkout" class="cm-btn cm-btn--primary cm-btn--block">Оформити</a>'
        + '</div>';
      pop.innerHTML = html;
    }

    // Hover-открытие dropdown
    if (wrap && pop) {
      var hideTimer = null;
      function show() {
        if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }
        renderPop();
        pop.removeAttribute('hidden');
      }
      function hide() {
        hideTimer = setTimeout(function () { pop.setAttribute('hidden', ''); }, 200);
      }
      wrap.addEventListener('mouseenter', show);
      wrap.addEventListener('mouseleave', hide);
      pop.addEventListener('mouseenter', show);
      pop.addEventListener('mouseleave', hide);
    }

    updateTotal();
    window.addEventListener('celena:cart:changed', function () {
      updateTotal();
      if (pop && !pop.hasAttribute('hidden')) renderPop();
    });
    window.addEventListener('storage', function (e) {
      if (e.key === 'celena_shop_cart') {
        updateTotal();
        if (pop && !pop.hasAttribute('hidden')) renderPop();
      }
    });
  }

  /* ─────────────  Toast: красивое уведомление при добавлении  ───────────── */
  function initCartToast() {
    var box = document.querySelector('[data-cm-toasts]');
    if (!box) return;

    var prevQty = (function () {
      return readCartItems().reduce(function (s, it) {
        return s + (parseInt(it.quantity, 10) || 0);
      }, 0);
    })();

    function showToast(item) {
      var t = document.createElement('div');
      t.className = 'cm-toast';
      var img = item && item.cover
        ? '<div class="cm-toast__img"><img src="' + escapeHtml(item.cover) + '" alt=""></div>'
        : '<div class="cm-toast__img"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg></div>';
      var label = item && item.variation_label
        ? '<small>' + escapeHtml(item.variation_label) + '</small>' : '';
      t.innerHTML =
        '<div class="cm-toast__check"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg></div>'
        + img
        + '<div class="cm-toast__body">'
        +   '<div class="cm-toast__head">Товар додано до кошика</div>'
        +   '<div class="cm-toast__title">' + escapeHtml((item && item.title) || '') + '</div>'
        +   label
        + '</div>'
        + '<div class="cm-toast__actions">'
        +   '<a href="/cart" class="cm-btn cm-btn--primary">До кошика</a>'
        +   '<a href="/checkout" class="cm-btn cm-btn--ghost">Оформити</a>'
        + '</div>'
        + '<button type="button" class="cm-toast__close" aria-label="Закрити"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18M6 6l12 12"/></svg></button>';
      box.appendChild(t);
      requestAnimationFrame(function () { t.classList.add('is-show'); });
      var dismiss = function () {
        t.classList.remove('is-show');
        setTimeout(function () { if (t.parentNode) t.parentNode.removeChild(t); }, 250);
      };
      var closeBtn = t.querySelector('.cm-toast__close');
      if (closeBtn) closeBtn.addEventListener('click', dismiss);
      setTimeout(dismiss, 5000);
    }

    function onChange() {
      var items = readCartItems();
      var newQty = items.reduce(function (s, it) { return s + (parseInt(it.quantity, 10) || 0); }, 0);
      if (newQty > prevQty) {
        // Берём последний товар (добавленный недавно — он в конце items)
        var last = items[items.length - 1];
        showToast(last);
      }
      prevQty = newQty;
    }

    window.addEventListener('celena:cart:changed', onChange);
  }

  /* ─────────────  Add-to-cart guard: подменяет data-pprice если кривое  ─────────────
     Capture-phase listener ДО shop.js. Не блокирует клик — только пытается
     найти валидную цену из нескольких источников: data-pprice → data-price
     родительской карточки → текст .cm-price-now. Не показывает alert. */
  function parsePriceText(s) {
    if (!s) return NaN;
    // "9 800.00 ₴" → 9800.00; "9,800" → 9800
    var clean = String(s).replace(/[^\d.,\-]/g, '').replace(/\s+/g, '');
    // Если есть и точка и запятая — точка десятичная, запятые — тысячи
    if (clean.indexOf('.') >= 0 && clean.indexOf(',') >= 0) {
      clean = clean.replace(/,/g, '');
    } else if (clean.indexOf(',') >= 0 && clean.indexOf('.') < 0) {
      clean = clean.replace(',', '.');
    }
    var n = parseFloat(clean);
    return isNaN(n) ? NaN : n;
  }

  function initAddToCartGuard() {
    document.addEventListener('click', function (e) {
      var btn = e.target.closest && e.target.closest('[data-add-to-cart]');
      if (!btn) return;
      var price = parseFloat(btn.getAttribute('data-pprice'));
      if (!isNaN(price) && price > 0) return; // всё хорошо

      // Пытаемся найти цену из разных источников:
      var found = NaN;
      var card = btn.closest('[data-cm-product]');
      // 1) data-price родительской карточки
      if (card) {
        var v = parseFloat(card.dataset.price);
        if (!isNaN(v) && v > 0) found = v;
      }
      // 2) текст элемента с актуальной ценой в карточке
      if (isNaN(found) && card) {
        var pn = card.querySelector('[data-cm-price-now], [data-cm-price-now-big], .cm-price-now');
        if (pn) {
          var t = parsePriceText(pn.textContent);
          if (!isNaN(t) && t > 0) found = t;
        }
      }
      // 3) на странице товара — корневой контейнер
      if (isNaN(found)) {
        var pp = document.querySelector('.cm-pp[data-cm-product]');
        if (pp) {
          var v2 = parseFloat(pp.dataset.price);
          if (!isNaN(v2) && v2 > 0) found = v2;
        }
      }
      if (!isNaN(found) && found > 0) {
        btn.setAttribute('data-pprice', String(found));
      }
      // Если не нашли — НЕ блокируем. shop.js добавит как есть (с 0 при необходимости),
      // юзер увидит проблему в корзине и сможет её исправить.
    }, true);
  }

  /* ─────────────  Checkout form: клиентская валидация ─────────────
     1) Корзина не пустая (если пустая — редирект на /cart)
     2) HTML5 required (имя, email)
     3) Email формат
     Перехват в capture-фазе чтобы успеть до shop.js attachCheckoutForm. */
  function initCheckoutValidation() {
    var form = document.querySelector('[data-checkout-form]');
    if (!form) return;
    form.setAttribute('novalidate', 'novalidate'); // отключаем нативные popup, рисуем сами

    function showError(field, message) {
      var wrap = field.closest('.cm-form-field, .cm-radio-group') || field.parentElement;
      if (!wrap) return;
      wrap.classList.add('is-error');
      var msg = wrap.querySelector('.cm-form-field__error');
      if (!msg) {
        msg = document.createElement('div');
        msg.className = 'cm-form-field__error';
        wrap.appendChild(msg);
      }
      msg.textContent = message;
    }

    function clearErrors() {
      form.querySelectorAll('.is-error').forEach(function (el) { el.classList.remove('is-error'); });
      form.querySelectorAll('.cm-form-field__error').forEach(function (el) { el.remove(); });
    }

    form.addEventListener('submit', function (e) {
      clearErrors();

      // 1) Cart not empty
      var items = readCartItems();
      if (items.length === 0) {
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        alert('Кошик порожній. Додайте товари перед оформленням.');
        window.location.href = '/cart';
        return;
      }

      // 2) Required fields
      var name  = form.querySelector('input[name="name"]');
      var email = form.querySelector('input[name="email"]');
      var ok = true;

      if (name && !name.value.trim()) {
        showError(name, 'Вкажіть ваше ім\'я');
        if (ok) name.focus();
        ok = false;
      }
      if (email) {
        var v = email.value.trim();
        var re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!v) {
          showError(email, 'Вкажіть email для зв\'язку');
          if (ok) email.focus();
          ok = false;
        } else if (!re.test(v)) {
          showError(email, 'Перевірте формат email');
          if (ok) email.focus();
          ok = false;
        }
      }

      if (!ok) {
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        // Прокрутка к первой ошибке
        var firstErr = form.querySelector('.is-error');
        if (firstErr) firstErr.scrollIntoView({ behavior: 'smooth', block: 'center' });
      }
    }, true);

    // Снимаем подсветку ошибки при изменении значения
    form.addEventListener('input', function (e) {
      var wrap = e.target.closest && e.target.closest('.is-error');
      if (wrap) {
        wrap.classList.remove('is-error');
        var msg = wrap.querySelector('.cm-form-field__error');
        if (msg) msg.remove();
      }
    });
  }

  /* ─────────────  Checkout summary — рендер в commerce-стиле через Cart API  ───────────── */
  function initCommerceCheckoutSummary() {
    var box = document.querySelector('[data-cm-checkout-items]');
    if (!box) return;

    var qtyEl     = document.querySelector('[data-cm-checkout-qty]');
    var subEl     = document.querySelector('[data-cm-checkout-subtotal]');
    var totEl     = document.querySelector('[data-cm-checkout-total]');
    var shipRow   = document.querySelector('[data-cm-checkout-shipping-row]');
    var shipEl    = document.querySelector('[data-cm-checkout-shipping]');
    var shipTitle = document.querySelector('[data-cm-checkout-shipping-title]');
    var discRow   = document.querySelector('[data-cm-checkout-discount-row]');
    var discEl    = document.querySelector('[data-cm-checkout-discount]');
    var discCode  = document.querySelector('[data-cm-checkout-promo-code]');

    // Используем единый форматтер из shop.js — он читает символ/позицию/decimals из window.CELENA_SHOP.
    function priceFmt(n) {
      return (window.CelenaShop && window.CelenaShop.price)
        ? window.CelenaShop.price(n)
        : (fmt(n) + ' ₴');
    }

    function readShipping() {
      var sel = document.querySelector('input[name="shipping_method_id"]:checked');
      if (!sel) return { id: 0, cost: 0, title: '' };
      var label = sel.closest('label');
      var titleEl = label && label.querySelector('.cm-radio__title');
      return {
        id: parseInt(sel.value, 10) || 0,
        cost: parseFloat(sel.getAttribute('data-cost')) || 0,
        title: titleEl ? titleEl.textContent.trim() : ''
      };
    }

    var lastPromo = null;
    function getPromo() {
      if (lastPromo) return lastPromo;
      return (window.Celena && window.Celena.Shop && window.Celena.Shop.checkout && window.Celena.Shop.checkout.promo) || null;
    }

    function render() {
      var items = readCartItems();
      if (items.length === 0) {
        box.innerHTML = '<p class="cm-checkout-loading">Кошик порожній. <a href="/shop">До магазину</a></p>';
        if (qtyEl) qtyEl.textContent = '0';
        if (subEl) subEl.textContent = priceFmt(0);
        if (totEl) totEl.textContent = priceFmt(0);
        if (shipRow) shipRow.hidden = true;
        if (discRow) discRow.hidden = true;
        return;
      }
      var subtotal = 0;
      var totalQty = 0;
      var html = '';
      items.forEach(function (it) {
        var qty = parseInt(it.quantity, 10) || 0;
        var price = parseFloat(it.price) || 0;
        var sub = qty * price;
        subtotal += sub;
        totalQty += qty;
        var label = it.variation_label ? ' <small>(' + escapeHtml(it.variation_label) + ')</small>' : '';
        html += '<div class="cm-checkout-line">'
          +   '<span class="cm-checkout-line__title">' + escapeHtml(it.title || '') + label + '</span>'
          +   '<span class="cm-checkout-line__qty">×' + qty + '</span>'
          +   '<span class="cm-checkout-line__sub">' + priceFmt(sub) + '</span>'
          + '</div>';
      });
      box.innerHTML = html;
      if (qtyEl) qtyEl.textContent = String(totalQty);
      if (subEl) subEl.textContent = priceFmt(subtotal);

      var shipping = readShipping();
      var promo = getPromo();
      var discount = 0;
      var shipCost = shipping.cost;
      if (promo) {
        if (promo.free_shipping) shipCost = 0;
        else if (typeof promo.discount === 'number') discount = Math.min(subtotal, promo.discount);
      }
      if (shipRow) {
        if (shipping.id > 0) {
          shipRow.hidden = false;
          if (shipTitle) shipTitle.textContent = shipping.title ? '(' + shipping.title + ')' : '';
          if (shipEl)    shipEl.textContent = shipCost > 0 ? priceFmt(shipCost) : 'безкоштовно';
        } else {
          shipRow.hidden = true;
        }
      }
      if (discRow) {
        if (discount > 0) {
          discRow.hidden = false;
          if (discCode) discCode.textContent = promo && promo.code ? '(' + promo.code + ')' : '';
          if (discEl)   discEl.textContent = '−' + priceFmt(discount);
        } else {
          discRow.hidden = true;
        }
      }
      var grand = Math.max(0, subtotal - discount + shipCost);
      if (totEl) {
        var prev = totEl.textContent;
        var next = priceFmt(grand);
        totEl.textContent = next;
        if (prev && prev !== next && prev !== '—') {
          totEl.classList.remove('cm-checkout-total--bump');
          // reflow trick — заставляем браузер увидеть удаление класса перед добавлением
          void totEl.offsetWidth;
          totEl.classList.add('cm-checkout-total--bump');
        }
      }
    }

    render();
    window.addEventListener('celena:cart:changed', render);
    window.addEventListener('storage', function (e) {
      if (e.key === 'celena_shop_cart') render();
    });
    document.addEventListener('change', function (e) {
      if (e.target && e.target.matches && e.target.matches('input[name="shipping_method_id"]')) {
        render();
      }
    });
    // shop.js перерисовывает summary через renderCheckoutSummary; ловим тот же change промокода.
    function onPromoChanged(e) {
      if (e && e.detail && 'promo' in e.detail) {
        lastPromo = e.detail.promo;
      } else {
        lastPromo = null; // упадём обратно на чтение из window.Celena.Shop.checkout
      }
      render();
    }
    window.addEventListener('celena:promo:changed', onPromoChanged);
    document.addEventListener('celena:promo:changed', onPromoChanged);
  }

  /* ─────────────  Reviews: подсветка звёзд по data-cm-stars-fill, форма-toggle, рейтинг-выбор  ───────────── */
  function initStarFills() {
    document.querySelectorAll('[data-cm-stars-fill]').forEach(function (el) {
      var v = parseFloat(el.getAttribute('data-cm-stars-fill')) || 0;
      var stars = el.querySelectorAll('svg');
      stars.forEach(function (s, i) {
        s.style.fill = (i + 0.5 <= v) ? '#f5b400' : 'var(--cm-border-strong)';
      });
    });
  }

  function initReviewForm() {
    var form = document.querySelector('[data-cm-review-form]');
    var toggles = document.querySelectorAll('[data-cm-review-toggle]');
    if (!form || !toggles.length) return;

    toggles.forEach(function (t) {
      t.addEventListener('click', function () {
        form.hidden = !form.hidden;
        if (!form.hidden) {
          form.scrollIntoView({ behavior: 'smooth', block: 'center' });
          var first = form.querySelector('input[name="author_name"]');
          if (first) setTimeout(function () { first.focus(); }, 250);
        }
      });
    });

    // Интерактивный выбор рейтинга
    var rate = form.querySelector('[data-cm-review-rate]');
    if (rate) {
      var input = rate.querySelector('[data-cm-review-rate-value]');
      var stars = rate.querySelectorAll('[data-cm-review-star]');
      function setRating(n) {
        if (input) input.value = String(n);
        stars.forEach(function (s) {
          var v = parseInt(s.getAttribute('data-cm-review-star'), 10);
          s.classList.toggle('is-active', v <= n);
        });
      }
      stars.forEach(function (s) {
        s.addEventListener('click', function () {
          setRating(parseInt(s.getAttribute('data-cm-review-star'), 10) || 5);
        });
        s.addEventListener('mouseenter', function () {
          var n = parseInt(s.getAttribute('data-cm-review-star'), 10) || 0;
          stars.forEach(function (x) {
            var v = parseInt(x.getAttribute('data-cm-review-star'), 10);
            x.classList.toggle('is-active', v <= n);
          });
        });
      });
      rate.addEventListener('mouseleave', function () {
        setRating(parseInt(input.value, 10) || 5);
      });
    }
  }

  function initReviewAvatars() {
    document.querySelectorAll('.cm-review__avatar').forEach(function (a) {
      var name = (a.textContent || '').trim();
      var letter = name ? name.charAt(0).toUpperCase() : '?';
      a.setAttribute('data-letter', letter);
      a.textContent = '';
    });
  }

  /* ─────────────  Product page: tabs scrollspy (характеристики/опис/відгуки)  ───────────── */
  function initProductTabs() {
    var tabs = document.querySelectorAll('[data-cm-pp-tabs] .cm-pp-tab');
    if (!tabs.length) return;
    var sections = [];
    tabs.forEach(function (t) {
      var href = t.getAttribute('href') || '';
      if (href.charAt(0) !== '#') return;
      var el = document.getElementById(href.substring(1));
      if (el) sections.push({ tab: t, el: el });
    });
    if (!sections.length) return;

    tabs.forEach(function (t) {
      t.addEventListener('click', function (e) {
        var href = t.getAttribute('href') || '';
        if (href.charAt(0) !== '#') return;
        var target = document.getElementById(href.substring(1));
        if (!target) return;
        e.preventDefault();
        target.scrollIntoView({ behavior: 'smooth', block: 'start' });
      });
    });

    function onScroll() {
      var scrollY = window.scrollY + 120;
      var current = sections[0];
      sections.forEach(function (s) {
        if (s.el.offsetTop <= scrollY) current = s;
      });
      tabs.forEach(function (t) { t.classList.toggle('is-active', t === current.tab); });
    }
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
  }

  ready(function () {
    initHeroSlider();
    initCarousels();
    initCategoryCarousel();
    initCatalog();
    initProductPricing();
    initProductVariations();
    initProductHover();
    initViewed();
    initCountdown();
    initCartCounter();
    initAboutToggle();
    initShowMore();
    initCategorySort();
    initFilterCheckboxes();
    initPriceSlider();
    initFilterMobileToggle();
    initActiveFilterRemove();
    initViewToggle();
    initProductGallery();
    initProductTabs();
    initStarFills();
    initReviewForm();
    initReviewAvatars();
    initAddToCartGuard();
    initCartMini();
    initCartToast();
    initCommerceCart();
    initCheckoutValidation();
    initCommerceCheckoutSummary();
  });
})();
