بچسبان)
----------------------------------------------------------
کار: اگر عکسهای محصول بیشتر از ۴ تا بود، فقط ۳ تای اول
+ یک کاشی «+N» نشان میدهد (بدون اسکرول).
کلیک روی کاشی = باز شدن لایتباکس PhotoSwipe خود ووکامرس
با همه عکسها. برای تغییر تعداد: عدد VISIBLE را عوض کن.
========================================================== */
(function () {
var VISIBLE = 4; // تعداد خانههای تامبنیل (آخرین خانه = کاشی +N)
function openGallery(gal) {
var trigger = gal.querySelector('.woocommerce-product-gallery__trigger');
if (trigger) { trigger.click(); return; }
var link = gal.querySelector('.woocommerce-product-gallery__image a');
if (link) { link.click(); }
}
function upgrade(gal) {
if (gal.dataset.sgDone) return;
var list = gal.querySelector('.flex-control-thumbs');
if (!list) return;
var items = Array.prototype.slice.call(list.querySelectorAll(':scope > li'));
if (items.length <= VISIBLE) return;
gal.dataset.sgDone = '1';
var keep = items.slice(0, VISIBLE - 1);
var rest = items.slice(VISIBLE - 1);
rest.forEach(function (li) { li.classList.add('sg-hidden'); });
var previewImg = rest[0].querySelector('img');
var tile = document.createElement('li');
tile.className = 'sg-more';
tile.setAttribute('data-more', '+' + rest.length);
tile.title = 'مشاهده همه عکسها';
if (previewImg) {
var img = document.createElement('img');
img.src = previewImg.src;
img.alt = 'مشاهده همه عکسها';
tile.appendChild(img);
}
tile.addEventListener('click', function () { openGallery(gal); });
list.appendChild(tile);
}
function run() {
document
.querySelectorAll('.wpb_row.product .woocommerce-product-gallery')
.forEach(upgrade);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', run);
} else {
run();
}
// اگر گالری با تاخیر ساخته شد (کش/لیزیلود)، چند بار دیگر تلاش کن
var tries = 0;
var timer = setInterval(function () {
run();
if (++tries > 10) clearInterval(timer);
}, 800);
// هوک عمومی: بعد از تعویض عکسها با AJAX (مثلاً انتخاب واریشن در محصول
// متغیردار که گالری را عوض میکند) صدا بزن: window.sgRefreshProductGallery()
window.sgRefreshProductGallery = run;
})();
/* ==========================================================
بخش ۲: جمعکردن شناسه/دستهبندی/برچسبها در یک خط
کلیک روی دکمه «نمایش همه» همه موارد را باز میکند.
----------------------------------------------------------
نتیجه تحقیق سئو (انجام شد، مشکلی نیست):
۱) همه لینکها در HTML سمت سرور میمانند؛ CSS/JS فقط ظاهر
را جمع میکند. بدون جاوااسکریپت هم همه موارد دیده میشوند.
۲) گوگل صفحه را با رندر کامل (CSS+JS) بررسی و محتوای
جمعشده/تبی را بهطور کامل ایندکس میکند (تأیید رسمی
گوگل از ۲۰۱۶ به بعد، چند بار تکرار شده). لینکهای داخل
بخش جمعشده هم کرال و دنبال میشوند.
۳) چون محتوا برای کاربر و ربات یکسان است، cloaking محسوب
نمیشود. فقط لینکها را nofollow نکن و URLها را عوض نکن.
========================================================== */
(function () {
function setup(box) {
if (!box || box.dataset.sgMetaDone) return;
var links = Array.prototype.slice.call(box.querySelectorAll('a'));
if (links.length < 4) return; // مورد کم است؛ جمعکردن لازم نیست
box.dataset.sgMetaDone = '1';
box.classList.add('sg-meta', 'sg-collapsed');
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'sg-meta-toggle';
btn.title = 'نمایش همه';
function paint() {
var open = box.classList.contains('sg-open');
btn.setAttribute('aria-expanded', open ? 'true' : 'false');
btn.textContent = open ? 'بستن ✕' : '+' + Math.max(hiddenCount, 1);
}
var hiddenCount = 0;
function measure() {
// قفل عرض: بدون آن، بازکردن موقت لینکها max-content را بالا میبرد،
// فلکس ستونها کش میآید و سنجش در عرض غیرواقعی انجام میشود
// (نتیجه: خط دوم هرگز بریده نمیشد).
var lockW = box.clientWidth;
box.style.maxWidth = lockW + 'px';
box.querySelectorAll('.sg-cut').forEach(function (el) { el.classList.remove('sg-cut'); });
void box.offsetHeight; // رفرش چیدمان
var baseTop = btn.offsetTop;
hiddenCount = 0;
var cutRest = false;
links.forEach(function (a) {
if (cutRest || a.offsetTop > baseTop + 4) {
cutRest = true;
a.classList.add('sg-cut');
hiddenCount++;
}
});
// لیبل گروهی که همه لینکهایش مخفی شد، مخفی شود
box.querySelectorAll('.w-post-elm').forEach(function (g) {
var gl = g.querySelectorAll('a');
var before = g.querySelector('.w-post-elm-before');
if (gl.length && before) {
var allHidden = true;
gl.forEach(function (a) { if (!a.classList.contains('sg-cut')) allHidden = false; });
before.classList.toggle('sg-cut', allHidden);
}
});
box.style.maxWidth = ''; // بازکردن قفل عرض
}
function recalc() {
if (!box.classList.contains('sg-collapsed')) return;
// دو پاس: متن دکمه بعد از paint عریضتر میشود و چیدمان را جابهجا
// میکند؛ پاس دوم با عرض نهایی دکمه میسنجد تا همیشه تکخط بماند.
// حداکثر ۳ پاس تا پایداری شمارش.
var last = -1;
for (var p = 0; p < 3; p++) {
measure();
paint();
if (hiddenCount === last) break;
last = hiddenCount;
}
}
btn.addEventListener('click', function () {
var open = box.classList.toggle('sg-open');
box.classList.toggle('sg-collapsed', !open);
if (!open) recalc();
else box.querySelectorAll('.sg-cut').forEach(function (el) { el.classList.remove('sg-cut'); });
paint();
});
box.insertBefore(btn, box.firstChild);
recalc();
var rT;
window.addEventListener('resize', function () {
clearTimeout(rT);
rT = setTimeout(recalc, 200);
});
if (document.fonts && document.fonts.ready) document.fonts.ready.then(recalc);
window.addEventListener('load', recalc);
// هر بازچیدمان داخلی (فونت، عکس، سایدبار، زوم) → سنجش دوباره تا تکخط بماند
if ('ResizeObserver' in window) {
var roT;
try {
new ResizeObserver(function () {
clearTimeout(roT);
roT = setTimeout(recalc, 150);
}).observe(box);
} catch (e) {}
}
}
function run() {
document.querySelectorAll('.wpb_row.product').forEach(function (scope) {
var box = null;
scope.querySelectorAll('.w-hwrapper').forEach(function (w) {
if (w.querySelector('.post_taxonomy')) box = w;
});
setup(box);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', run);
} else {
run();
}
var tries = 0;
var timer = setInterval(function () {
run();
if (++tries > 10) clearInterval(timer);
}, 800);
})();
/* ==========================================================
بخش ۳: تبدیل تبها به آکاردئون در موبایل (تا ۷۶۸px)
از دکمهها و سکشنهای خود قالب استفاده میکند، پس با
منطق داخلی ایمپرزا/ووکامرس سازگار است.
========================================================== */
(function () {
var mq = window.matchMedia('(max-width: 768px)');
function syncItems(tabs) {
var secs = Array.prototype.slice.call(tabs.querySelectorAll('.w-tabs-section'));
var items = tabs.querySelectorAll('.w-tabs-item');
var idx = 0;
secs.forEach(function (s, i) { if (s.classList.contains('active')) idx = i; });
items.forEach(function (b, i) {
b.classList.toggle('active', i === idx);
b.setAttribute('aria-expanded', i === idx ? 'true' : 'false');
});
}
function apply(scope) {
var tabs = scope.querySelector('.w-tabs');
if (!tabs) return;
if (mq.matches) {
if (tabs.dataset.sgAcc === '1') return;
tabs.dataset.sgAcc = '1';
tabs.classList.add('sg-acc');
var secs = tabs.querySelectorAll('.w-tabs-section');
var seen = false;
secs.forEach(function (s) {
var c = s.querySelector('.w-tabs-section-content');
if (c) c.removeAttribute('hidden');
var h = s.querySelector('.w-tabs-section-header');
if (s.classList.contains('active')) {
if (!seen) { seen = true; if (h) h.setAttribute('aria-expanded', 'true'); }
else s.classList.remove('active');
} else if (h) h.setAttribute('aria-expanded', 'false');
});
if (!seen && secs[0]) {
secs[0].classList.add('active');
var h0 = secs[0].querySelector('.w-tabs-section-header');
if (h0) h0.setAttribute('aria-expanded', 'true');
}
syncItems(tabs);
} else {
if (tabs.dataset.sgAcc !== '1') return;
delete tabs.dataset.sgAcc;
tabs.classList.remove('sg-acc');
var act = tabs.querySelector('.w-tabs-section.active');
if (!act) {
var first = tabs.querySelector('.w-tabs-section');
if (first) first.classList.add('active');
}
syncItems(tabs);
}
}
document.addEventListener('click', function (e) {
var h = e.target.closest && e.target.closest('.sg-acc .w-tabs-section-header');
if (!h) return;
var tabs = h.closest('.w-tabs');
var sec = h.closest('.w-tabs-section');
var was = sec.classList.contains('active');
tabs.querySelectorAll('.w-tabs-section.active').forEach(function (s) {
s.classList.remove('active');
var hh = s.querySelector('.w-tabs-section-header');
if (hh) hh.setAttribute('aria-expanded', 'false');
});
if (!was) {
sec.classList.add('active');
h.setAttribute('aria-expanded', 'true');
}
syncItems(tabs);
});
function run() {
document.querySelectorAll('.wpb_row.product').forEach(apply);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', run);
} else {
run();
}
if (mq.addEventListener) mq.addEventListener('change', run);
else if (mq.addListener) mq.addListener(run);
})();
/* ==========================================================
بخش ۴: نوار خرید ثابت پایین صفحه (موبایل و تبلت)
تعداد + دکمه؛ دکمه، فرم اصلی را سابمیت میکند (سازگار با
محصولات متغیردار). وقتی باکس خرید دیده میشود، نوار مخفی است.
========================================================== */
(function () {
function setup(scope) {
if (scope.dataset.sgBarDone) return;
var form = scope.querySelector('form.cart');
var btn = scope.querySelector('.single_add_to_cart_button');
if (!form || !btn) return;
scope.dataset.sgBarDone = '1';
var bar = document.createElement('div');
bar.className = 'sg-buybar sg-hidden-bar';
bar.innerHTML = '';
var bbQty = bar.querySelector('.sg-bb-qty');
var bbBtn = bar.querySelector('.sg-buybar-btn');
bbBtn.textContent = (btn.textContent || '').trim() || 'افزودن به سبد خرید';
function origQty() { return scope.querySelector('form.cart .qty'); }
function syncFromOrig() { var o = origQty(); if (o && document.activeElement !== bbQty) bbQty.value = o.value; }
syncFromOrig();
function push(v) {
var o = origQty();
if (!o) return;
var min = parseFloat(o.getAttribute('min')) || 1;
v = Math.max(min, Math.floor(v) || min);
var max = parseFloat(o.getAttribute('max'));
if (max) v = Math.min(max, v);
o.value = v;
o.dispatchEvent(new Event('input', { bubbles: true }));
o.dispatchEvent(new Event('change', { bubbles: true }));
bbQty.value = v;
}
bar.querySelector('.sg-bb-minus').addEventListener('click', function () { push(parseFloat(bbQty.value || '1') - 1); });
bar.querySelector('.sg-bb-plus').addEventListener('click', function () { push(parseFloat(bbQty.value || '1') + 1); });
bbQty.addEventListener('change', function () { push(parseFloat(bbQty.value || '1')); });
document.addEventListener('input', function (e) { if (e.target === origQty()) syncFromOrig(); });
bbBtn.addEventListener('click', function () { btn.click(); });
document.body.appendChild(bar);
var box = scope.querySelector('.w-post-elm.add_to_cart') || form;
var ioFired = false;
function rectVisible() {
var r = box.getBoundingClientRect();
var vh = window.innerHeight || document.documentElement.clientHeight;
return r.bottom > vh * 0.05 && r.top < vh * 0.45;
}
function applyVisible(boxSeen) { bar.classList.toggle('sg-hidden-bar', boxSeen); }
if ('IntersectionObserver' in window) {
try {
new IntersectionObserver(function (entries) {
ioFired = true;
entries.forEach(function (en) { bar.classList.toggle('sg-hidden-bar', en.isIntersecting); });
}, { rootMargin: '-5% 0px -55% 0px' }).observe(box);
} catch (e) { ioFired = true; bar.classList.remove('sg-hidden-bar'); }
} else {
ioFired = true;
bar.classList.remove('sg-hidden-bar');
}
// فالبک: اگر IO دیر کرد یا اصلاً فایر نشد (برخی وبویوها/پیشنمایشها)،
// با getBoundingClientRect همگام شو — همان منطق، بدون تداخل با IO.
var fbT;
function fallback() {
if (ioFired) return;
applyVisible(rectVisible()); // دیده شدن باکس = مخفی شدن نوار (مثل IO)
}
window.addEventListener('scroll', function () { clearTimeout(fbT); fbT = setTimeout(fallback, 100); }, { passive: true });
if ('onscrollend' in window) window.addEventListener('scrollend', fallback);
window.addEventListener('resize', function () { clearTimeout(fbT); fbT = setTimeout(fallback, 100); });
setTimeout(fallback, 600);
}
function run() {
if (!window.matchMedia('(max-width: 1024px)').matches) return;
document.querySelectorAll('.wpb_row.product').forEach(setup);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', run);
} else {
run();
}
var tries = 0;
var timer = setInterval(function () {
run();
if (++tries > 10) clearInterval(timer);
}, 800);
})();
/* ==========================================================
بخش ۵: لیبل «تعداد:» کنار انتخابگر تعداد فرم خرید
(المان بیلدر لیبل ندارد؛ اسنیپت آن را میسازد تا در همه
حالتها — ساده/متغیر، دسکتاپ/موبایل — کنار استپر باشد)
========================================================== */
(function () {
function run() {
document.querySelectorAll('.wpb_row.product form.cart').forEach(function (form) {
if (form.querySelector(':scope > .sg-qty-label, .variations_button > .sg-qty-label')) return;
var q = form.querySelector('.plus-minus-button') || form.querySelector('.quantity');
if (!q || !q.parentNode) return;
var s = document.createElement('span');
s.className = 'sg-qty-label';
s.textContent = 'تعداد:';
q.parentNode.insertBefore(s, q);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', run);
} else {
run();
}
var tries = 0;
var timer = setInterval(function () {
run();
if (++tries > 10) clearInterval(timer);
}, 800);
})();
/* ==========================================================
بخش ۶: دایره درصد تخفیف کنار قیمت (متن سفید)
درصد را از روی قیمت خطخورده و قیمت جدید حساب میکند، پس با
عوض شدن واریشن هم بهروز میشود. اگر تخفیفی نباشد، دایره
ساخته/نشان داده نمیشود.
========================================================== */
(function () {
var FA = '۰۱۲۳۴۵۶۷۸۹';
function num(txt) {
var t = String(txt || '').replace(/[۰-۹]/g, function (d) { return FA.indexOf(d); }).replace(/[^0-9]/g, '');
return parseInt(t, 10) || 0;
}
function fa(txt) { return String(txt).replace(/[0-9]/g, function (d) { return FA[d]; }); }
function paint(scope) {
var panel = scope.querySelector('p.price');
if (!panel) return;
var wrap = panel.querySelector(':scope > .sg-off-wrap');
var del = panel.querySelector('del');
var ins = panel.querySelector('ins');
var oldV = del ? num(del.textContent) : 0;
var curV = num(ins ? ins.textContent : panel.textContent);
if (!oldV || !curV || curV >= oldV) {
// بدون تخفیف: دایره را بردار و del را از بستهبندی خارج کن
var oldEl = panel.querySelector('.sg-off-circle');
if (oldEl) oldEl.remove();
if (wrap && del && del.parentNode === wrap) { panel.insertBefore(del, wrap); wrap.remove(); }
else if (wrap && !del) wrap.remove();
return;
}
var txt = fa(Math.round((oldV - curV) / oldV * 100)) + '٪';
if (!wrap) {
wrap = document.createElement('span');
wrap.className = 'sg-off-wrap';
wrap.setAttribute('aria-hidden', 'true');
if (ins) panel.insertBefore(wrap, ins);
else panel.appendChild(wrap);
}
var el = wrap.querySelector(':scope > .sg-off-circle');
if (!el) {
el = document.createElement('span');
el.className = 'sg-off-circle';
el.setAttribute('dir', 'rtl');
wrap.insertBefore(el, wrap.firstChild);
} else if (wrap.firstChild !== el) {
wrap.insertBefore(el, wrap.firstChild); // دایره همیشه بالای عدد
}
if (del && del.parentNode !== wrap) wrap.appendChild(del);
if (el.textContent !== txt) el.textContent = txt; // فقط وقتی فرق کرد بنویس (جلوگیری از حلقه آبزرور)
}
function run() {
document.querySelectorAll('.wpb_row.product').forEach(paint);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', run);
} else {
run();
}
var tries = 0;
var timer = setInterval(function () {
run();
if (++tries > 10) clearInterval(timer);
}, 800);
// قیمت با انتخاب واریشن عوض میشود (ووکامرس) — دایره را همگام نگه دار
if ('MutationObserver' in window) {
try {
var obs = new MutationObserver(function () { run(); });
document.querySelectorAll('.wpb_row.product p.price').forEach(function (p) {
obs.observe(p, { childList: true, characterData: true, subtree: true });
});
} catch (e) { /* بدون آبزرور هم با تاخیرهای بالا پوشش داده میشود */ }
}
})();
/* ==========================================================
بخش ۷: نوار اعتماد بین کارت خرید و باکس تبها
۵ ویژگی (آیکون + متن) در یک خط؛ در همه عرضها تکخط میماند و
با سوایپ اسکرول میشود — بدون نوار اسکرول و بدون فلش.
متنها را از آرایه ITEMS عوض کن.
========================================================== */
(function () {
var ITEMS = [
{ t: 'ضمانت اصلی بودن کالا', i: 'shield' },
{ t: 'هفت روز ضمانت بازگشت کالا', i: 'box' },
{ t: 'امکان پرداخت در محل', i: 'pin' },
{ t: '۲۴ ساعته، ۷ روز هفته', i: 'head' },
{ t: 'امکان تحویل سریع', i: 'truck' }
];
function svg(inner) {
return '';
}
var ICONS = {
shield: '',
box: '',
pin: '',
head: '',
truck: ''
};
function run() {
var tabs = document.querySelector('.w-tabs');
if (!tabs || !tabs.parentNode) return;
if (tabs.dataset.sgPTrustDone) return;
tabs.dataset.sgPTrustDone = '1';
var bar = document.createElement('div');
bar.className = 'sg-p-trust';
bar.setAttribute('role', 'list');
bar.setAttribute('aria-label', 'مزیتهای خرید');
var h = '';
ITEMS.forEach(function (o) {
h += '