/* =========================================================================
* main.js — общ UI слой (хедър, търсене, тостове, продуктови карти, reveal)
* Фаза 2: остава почти непроменен — работи върху server-rendered HTML.
* ========================================================================= */
(function () {
'use strict';
function $(sel, root) { return (root || document).querySelector(sel); }
function $$(sel, root) { return Array.prototype.slice.call((root || document).querySelectorAll(sel)); }
/* XSS защита при рендериране на текст в DOM (правило от salex) */
function esc(s) {
return String(s).replace(/[&<>"']/g, function (c) {
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
});
}
window.ACM = window.ACM || {};
window.ACM.esc = esc;
/* ======================== Тостове (както в оригинала) =================== */
var toastTimer = null;
function showToast(message, opts) {
opts = opts || {};
var root = $('#toast-root');
if (!root) return;
root.innerHTML = '';
if (toastTimer) { clearTimeout(toastTimer); toastTimer = null; }
var box = document.createElement('div');
box.className = 'acm-toast pointer-events-auto flex w-full max-w-sm items-center gap-3 rounded-card bg-ink px-4 py-3 text-white shadow-lg';
var p = document.createElement('p');
p.className = 'flex-1 text-[14px]';
p.textContent = message;
box.appendChild(p);
if (opts.actionLabel) {
var act = document.createElement('button');
act.type = 'button';
act.className = 'shrink-0 rounded-control px-2 py-1 text-[14px] font-medium text-brand-green transition-colors hover:bg-white/10';
act.textContent = opts.actionLabel;
act.addEventListener('click', function () {
if (opts.onAction) opts.onAction();
dismiss();
});
box.appendChild(act);
}
var close = document.createElement('button');
close.type = 'button';
close.setAttribute('aria-label', 'Затвори');
close.className = 'grid h-8 w-8 shrink-0 place-items-center rounded-control text-white/70 transition-colors hover:bg-white/10 hover:text-white';
close.innerHTML = '';
close.addEventListener('click', dismiss);
box.appendChild(close);
root.appendChild(box);
function dismiss() {
if (box.parentNode) box.parentNode.removeChild(box);
if (toastTimer) { clearTimeout(toastTimer); toastTimer = null; }
}
toastTimer = setTimeout(dismiss, opts.actionLabel ? 6000 : 4000);
}
window.ACM.showToast = showToast;
/* ======================== Бадж на количката ============================ */
function updateCartBadge() {
if (!window.ACM.cart) return;
var n = window.ACM.cart.totalUnits();
$$('[data-cart-badge]').forEach(function (b) {
if (n > 0) { b.textContent = n > 99 ? '99+' : String(n); b.hidden = false; }
else { b.hidden = true; }
});
}
document.addEventListener('acm:cart-changed', updateCartBadge);
/* ======================== Хедър ======================================== */
function initHeader() {
/* Профил dropdown */
var pBtn = $('#profile-menu-btn'), pPanel = $('#profile-menu-panel'), pRoot = $('#profile-menu-root');
if (pBtn && pPanel) {
pBtn.addEventListener('click', function () {
var open = pPanel.hidden;
pPanel.hidden = !open;
pBtn.setAttribute('aria-expanded', String(open));
var chev = pBtn.querySelector('.lucide-chevron-down');
if (chev) chev.classList.toggle('rotate-180', open);
});
document.addEventListener('pointerdown', function (e) {
if (!pPanel.hidden && pRoot && !pRoot.contains(e.target)) closeProfile();
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeProfile();
});
}
function closeProfile() {
if (pPanel && !pPanel.hidden) {
pPanel.hidden = true;
pBtn.setAttribute('aria-expanded', 'false');
var chev = pBtn.querySelector('.lucide-chevron-down');
if (chev) chev.classList.remove('rotate-180');
}
}
/* Мобилно меню */
var mBtn = $('#mobile-menu-btn'), mPanel = $('#mobile-menu-panel'), mScrim = $('#mobile-menu-scrim'), mClose = $('#mobile-menu-close');
function setMenu(open) {
if (!mPanel) return;
mPanel.hidden = !open;
mScrim.hidden = !open;
mBtn.setAttribute('aria-expanded', String(open));
document.body.style.overflow = open ? 'hidden' : '';
}
if (mBtn) mBtn.addEventListener('click', function () { setMenu(mPanel.hidden); });
if (mClose) mClose.addEventListener('click', function () { setMenu(false); });
if (mScrim) mScrim.addEventListener('click', function () { setMenu(false); });
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && mPanel && !mPanel.hidden) setMenu(false);
});
/* Мобилно търсене */
var sBtn = $('#mobile-search-btn'), sRow = $('#mobile-search-row'), sInput = $('#mobile-search-input');
if (sBtn) sBtn.addEventListener('click', function () {
var open = sRow.hidden;
sRow.hidden = !open;
sBtn.setAttribute('aria-expanded', String(open));
if (open && sInput) sInput.focus();
});
if (sInput) sInput.addEventListener('keydown', function (e) {
if (e.key === 'Enter' && sInput.value.trim()) {
window.location.href = '/catalog/?q=' + encodeURIComponent(sInput.value.trim());
}
});
initSearchSuggest();
}
/* ======================== Търсене с подсказки ========================== */
function searchProducts(q) {
var list = window.ACM_PRODUCTS || [];
q = q.toLowerCase();
var byName = [], bySubst = [];
list.forEach(function (p) {
if (p.name.toLowerCase().indexOf(q) !== -1) byName.push(p);
else if ((p.activeSubstances || []).some(function (s) { return s.name.toLowerCase().indexOf(q) !== -1; })) bySubst.push(p);
});
return byName.concat(bySubst);
}
window.ACM.searchProducts = searchProducts;
function stockDotsHTML(p) {
var st = window.ACM.demo.stockStatus;
return '' +
['montana', 'staraZagora', 'ruse', 'sofia'].map(function (k) {
return '';
}).join('') + '';
}
function initSearchSuggest() {
var input = $('#search-input'), box = $('#search-suggest');
if (!input || !box) return;
function render() {
var q = input.value.trim();
if (q.length < 2) { box.hidden = true; box.innerHTML = ''; input.setAttribute('aria-expanded', 'false'); return; }
var res = searchProducts(q).slice(0, 5);
var html = res.map(function (p) {
return '' +
'' +
'' + esc(p.name) + '' +
'Арт. № ' + esc(p.artNumber) + '' +
'' + stockDotsHTML(p) + '';
}).join('');
html += '' +
'' +
'Виж всички резултати за „' + esc(q) + '"';
box.innerHTML = html;
box.hidden = false;
input.setAttribute('aria-expanded', 'true');
}
input.addEventListener('input', render);
input.addEventListener('focus', render);
input.addEventListener('keydown', function (e) {
if (e.key === 'Enter' && input.value.trim()) {
window.location.href = '/catalog/?q=' + encodeURIComponent(input.value.trim());
}
if (e.key === 'Escape') { box.hidden = true; }
});
document.addEventListener('pointerdown', function (e) {
var root = $('#search-root');
if (root && !root.contains(e.target)) box.hidden = true;
});
}
/* ======================== Продуктови карти / купуване =================== */
/* Картите нямат data-атрибути (идват от server-rendered HTML) —
* productId се извлича от линка на картата или от URL на страницата. */
function productIdFromCard(card) {
var a = card.querySelector('a[href^="/product/"]');
if (!a) return null;
var m = a.getAttribute('href').match(/^\/product\/([^\/]+)\//);
return m ? m[1] : null;
}
function productIdFromURL() {
var m = window.location.pathname.match(/^\/product\/([^\/]+)\//);
return m ? m[1] : null;
}
function initStepper(root, onChange) {
var input = root.querySelector('input[aria-label="Количество"]');
var btns = root.querySelectorAll('button');
if (!input || btns.length < 2) return null;
var minus = btns[0], plus = btns[btns.length - 1];
function val() { var v = parseInt(input.value, 10); return isNaN(v) ? 1 : Math.min(9999, Math.max(1, v)); }
function sync() {
input.value = String(val());
minus.disabled = val() <= 1;
plus.disabled = val() >= 9999;
if (onChange) onChange(val());
}
minus.addEventListener('click', function () { input.value = String(val() - 1); sync(); });
plus.addEventListener('click', function () { input.value = String(val() + 1); sync(); });
input.addEventListener('change', sync);
sync();
return { value: val };
}
function findAddButton(scope) {
return $$('button', scope).find(function (b) {
var t = b.textContent.trim();
return t === 'Добавяне' || t === 'Добавяне в количката';
}) || null;
}
function wireBuyControls(scope, productId) {
if (!productId || !window.ACM.findProduct(productId)) return;
var stepperRoot = $$('div', scope).find(function (d) {
return d.className && String(d.className).indexOf('items-stretch') !== -1 && d.querySelector('input[aria-label="Количество"]');
});
var stepper = stepperRoot ? initStepper(stepperRoot) : null;
var addBtn = findAddButton(scope);
if (addBtn) {
addBtn.addEventListener('click', function () {
var qty = stepper ? stepper.value() : 1;
var p = window.ACM.findProduct(productId);
window.ACM.cart.add(productId, qty);
showToast(p.name + ' е добавен в количката', {
actionLabel: 'Виж количката',
onAction: function () { window.location.href = '/cart/'; }
});
});
}
}
function initProductCards() {
/* Карти в списъци (начало, каталог): коренът е .group с линк към продукт */
$$('div.group').forEach(function (card) {
var id = productIdFromCard(card);
if (id && findAddButton(card)) wireBuyControls(card, id);
});
/* Продуктова страница: id от URL, контролите са в */
var pid = productIdFromURL();
if (pid) {
var main = $('main');
if (main && findAddButton(main)) wireBuyControls(main, pid);
}
}
/* ======================== Складови точки — подсказки ==================== */
function initStockTitles() {
$$('button[aria-label^="Склад "]').forEach(function (b) {
b.title = b.getAttribute('aria-label');
});
}
/* ======================== Reveal анимации ============================== */
function initReveal() {
var els = $$('[data-reveal]');
if (!els.length) return;
if (!('IntersectionObserver' in window)) {
els.forEach(function (el) { el.classList.add('revealed'); });
return;
}
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (en) {
if (en.isIntersecting) { en.target.classList.add('revealed'); io.unobserve(en.target); }
});
}, { rootMargin: '0px 0px -40px 0px', threshold: 0.05 });
els.forEach(function (el) { io.observe(el); });
}
/* ======================== Бутoни-навигация (hero CTA) =================== */
/* В оригинала са