first deployment of kikomiko with url photos
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
8d98304137
commit
ae3ed7c7b7
Vendored
+316
@@ -0,0 +1,316 @@
|
||||
import {
|
||||
hydrateCart,
|
||||
subscribe,
|
||||
getSnapshot,
|
||||
formatPrice,
|
||||
} from "/js/cart.js";
|
||||
|
||||
function qs(sel, root = document) {
|
||||
return root.querySelector(sel);
|
||||
}
|
||||
|
||||
function qsa(sel, root = document) {
|
||||
return [...root.querySelectorAll(sel)];
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s)
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """);
|
||||
}
|
||||
|
||||
export function initHeader() {
|
||||
hydrateCart();
|
||||
|
||||
const header = qs(".site-header");
|
||||
const mega = qs(".mega-panel");
|
||||
const backdrop = qs(".mega-backdrop");
|
||||
const drawer = qs(".mobile-drawer");
|
||||
const badge = qs("[data-cart-count]");
|
||||
const cartMenu = qs("[data-cart-menu]");
|
||||
const cartTrigger = qs("[data-cart-trigger]");
|
||||
const cartPreview = qs("[data-cart-preview]");
|
||||
const cartList = qs("[data-cart-preview-list]");
|
||||
const cartEmpty = qs("[data-cart-preview-empty]");
|
||||
const cartFoot = qs("[data-cart-preview-foot]");
|
||||
const cartTotal = qs("[data-cart-preview-total]");
|
||||
|
||||
const fineHover =
|
||||
typeof window.matchMedia === "function" &&
|
||||
window.matchMedia("(hover: hover) and (pointer: fine)").matches;
|
||||
|
||||
function syncBadge(snap) {
|
||||
if (!badge) return;
|
||||
badge.textContent = String(snap.itemCount);
|
||||
badge.classList.toggle("is-visible", snap.hydrated && snap.itemCount > 0);
|
||||
}
|
||||
|
||||
function renderCartPreview(snap) {
|
||||
if (!cartList || !cartEmpty || !cartFoot || !cartTotal) return;
|
||||
const empty = !snap.items.length;
|
||||
cartEmpty.hidden = !empty;
|
||||
cartFoot.hidden = empty;
|
||||
cartList.hidden = empty;
|
||||
cartTotal.textContent = formatPrice(snap.subtotal);
|
||||
|
||||
if (empty) {
|
||||
cartList.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
|
||||
cartList.innerHTML = snap.items
|
||||
.slice(0, 8)
|
||||
.map((item) => {
|
||||
const href = `/products/${escapeHtml(item.slug)}/`;
|
||||
const img = item.image
|
||||
? `<img src="${escapeHtml(item.image)}" alt="" width="52" height="66" loading="lazy" />`
|
||||
: `<div class="cart-preview-ph" aria-hidden="true"></div>`;
|
||||
return `<a class="cart-preview-line" href="${href}">
|
||||
${img}
|
||||
<div class="cart-preview-meta">
|
||||
<p class="cart-preview-name">${escapeHtml(item.name)}</p>
|
||||
<p class="cart-preview-qty">× ${item.quantity.toLocaleString("fa-IR")}</p>
|
||||
</div>
|
||||
<p class="cart-preview-price">${formatPrice(item.price * item.quantity)}</p>
|
||||
</a>`;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
function setCartPreviewOpen(open) {
|
||||
if (!cartMenu || !cartPreview || !cartTrigger) return;
|
||||
cartMenu.classList.toggle("is-open", open);
|
||||
cartPreview.classList.toggle("is-open", open);
|
||||
if (open) cartPreview.removeAttribute("hidden");
|
||||
else cartPreview.setAttribute("hidden", "");
|
||||
cartTrigger.setAttribute("aria-expanded", open ? "true" : "false");
|
||||
}
|
||||
|
||||
function closeCartPreview() {
|
||||
setCartPreviewOpen(false);
|
||||
}
|
||||
|
||||
function openCartPreview() {
|
||||
setCartPreviewOpen(true);
|
||||
}
|
||||
|
||||
const sideNav = qs(".side-nav");
|
||||
const sideNavToggle = qs("[data-side-nav-toggle]");
|
||||
const SIDE_NAV_KEY = "kikomiko-side-nav-collapsed";
|
||||
|
||||
function setSideNavCollapsed(collapsed) {
|
||||
if (!sideNav || !sideNavToggle) return;
|
||||
sideNav.classList.toggle("is-collapsed", collapsed);
|
||||
sideNavToggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
||||
const label = collapsed ? "نمایش منو" : "پنهان کردن منو";
|
||||
sideNavToggle.setAttribute("aria-label", label);
|
||||
sideNavToggle.setAttribute("title", label);
|
||||
try {
|
||||
localStorage.setItem(SIDE_NAV_KEY, collapsed ? "1" : "0");
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
if (collapsed) closeMega();
|
||||
}
|
||||
|
||||
function hideDesktopMenu() {
|
||||
closeMega();
|
||||
setSideNavCollapsed(true);
|
||||
}
|
||||
|
||||
subscribe((snap) => {
|
||||
syncBadge(snap);
|
||||
renderCartPreview(snap);
|
||||
});
|
||||
const initial = getSnapshot();
|
||||
syncBadge(initial);
|
||||
renderCartPreview(initial);
|
||||
|
||||
if (cartMenu && cartTrigger && cartPreview) {
|
||||
cartTrigger.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const open = cartMenu.classList.contains("is-open");
|
||||
if (open) closeCartPreview();
|
||||
else {
|
||||
closeMega();
|
||||
openCartPreview();
|
||||
}
|
||||
});
|
||||
|
||||
if (fineHover) {
|
||||
let leaveTimer = 0;
|
||||
cartMenu.addEventListener("mouseenter", () => {
|
||||
window.clearTimeout(leaveTimer);
|
||||
openCartPreview();
|
||||
});
|
||||
cartMenu.addEventListener("mouseleave", () => {
|
||||
leaveTimer = window.setTimeout(closeCartPreview, 180);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
if (!(e.target instanceof Element)) return;
|
||||
if (!cartMenu.contains(e.target)) closeCartPreview();
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") closeCartPreview();
|
||||
});
|
||||
}
|
||||
|
||||
function closeMega() {
|
||||
mega?.classList.remove("is-open");
|
||||
mega?.removeAttribute("data-active");
|
||||
backdrop?.classList.remove("is-open");
|
||||
backdrop?.setAttribute("hidden", "");
|
||||
header?.classList.remove("is-open");
|
||||
qsa("[data-mega-key]").forEach((b) => b.classList.remove("is-active"));
|
||||
qsa("[data-mega-pane]").forEach((pane) => {
|
||||
pane.hidden = true;
|
||||
});
|
||||
// Restore home highlight when on home
|
||||
const home = qs('.side-nav-link[href="/"]');
|
||||
if (home && (location.pathname === "/" || location.pathname === "/fa")) {
|
||||
home.classList.add("is-active");
|
||||
}
|
||||
}
|
||||
|
||||
function openMega(key) {
|
||||
if (!mega) return;
|
||||
closeCartPreview();
|
||||
// Keep the side panel open while browsing mega categories
|
||||
if (sideNav?.classList.contains("is-collapsed")) {
|
||||
setSideNavCollapsed(false);
|
||||
}
|
||||
mega.setAttribute("data-active", key);
|
||||
mega.classList.add("is-open");
|
||||
header?.classList.add("is-open");
|
||||
backdrop?.classList.add("is-open");
|
||||
backdrop?.removeAttribute("hidden");
|
||||
qsa("[data-mega-pane]").forEach((pane) => {
|
||||
pane.hidden = pane.getAttribute("data-mega-pane") !== key;
|
||||
});
|
||||
qsa(".side-nav-link").forEach((b) => b.classList.remove("is-active"));
|
||||
qsa("[data-mega-key]").forEach((b) =>
|
||||
b.classList.toggle("is-active", b.getAttribute("data-mega-key") === key)
|
||||
);
|
||||
}
|
||||
|
||||
qsa("[data-mega-key]").forEach((btn) => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const key = btn.getAttribute("data-mega-key");
|
||||
if (!key) return;
|
||||
const open = mega?.getAttribute("data-active") === key;
|
||||
if (open) {
|
||||
closeMega();
|
||||
return;
|
||||
}
|
||||
openMega(key);
|
||||
});
|
||||
});
|
||||
|
||||
// Close mega immediately when any link inside it is chosen
|
||||
// (categories, view-all, featured products, collection title).
|
||||
mega?.addEventListener(
|
||||
"click",
|
||||
(e) => {
|
||||
const link = e.target instanceof Element ? e.target.closest("a[href]") : null;
|
||||
if (!link || !mega.contains(link)) return;
|
||||
hideDesktopMenu();
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
// Home / offers links in side nav should close mega + collapse panel
|
||||
qsa(".side-nav-link[href]").forEach((link) => {
|
||||
link.addEventListener("click", () => hideDesktopMenu());
|
||||
});
|
||||
|
||||
// Highlight active offers item
|
||||
if (location.pathname.startsWith("/offers")) {
|
||||
qsa(".side-nav-link").forEach((el) => el.classList.remove("is-active"));
|
||||
qs(
|
||||
'.side-nav-link[href="/offers/"], .side-nav-link[href="/offers"]'
|
||||
)?.classList.add("is-active");
|
||||
}
|
||||
|
||||
// Do not use mouseleave — it fights with clicks on mega links.
|
||||
backdrop?.addEventListener("click", closeMega);
|
||||
qs("[data-mega-close]")?.addEventListener("click", closeMega);
|
||||
|
||||
function closeDrawer(e) {
|
||||
e?.preventDefault?.();
|
||||
e?.stopPropagation?.();
|
||||
drawer?.classList.remove("is-open");
|
||||
drawer?.setAttribute("aria-hidden", "true");
|
||||
document.body.style.overflow = "";
|
||||
qsa("[data-mobile-sub]").forEach((p) => p.classList.remove("is-open"));
|
||||
qsa(".mobile-cat").forEach((c) => c.classList.remove("is-expanded"));
|
||||
}
|
||||
|
||||
function openDrawer(e) {
|
||||
e?.preventDefault?.();
|
||||
drawer?.classList.add("is-open");
|
||||
drawer?.setAttribute("aria-hidden", "false");
|
||||
document.body.style.overflow = "hidden";
|
||||
closeMega();
|
||||
closeCartPreview();
|
||||
}
|
||||
|
||||
qs("[data-menu-open]")?.addEventListener("click", openDrawer);
|
||||
qsa("[data-menu-close]").forEach((el) => {
|
||||
el.addEventListener("click", closeDrawer);
|
||||
});
|
||||
|
||||
qsa("[data-mobile-expand]").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
const id = btn.getAttribute("data-mobile-expand");
|
||||
const panel = qs(`[data-mobile-sub="${id}"]`);
|
||||
const cat = btn.closest(".mobile-cat");
|
||||
const open = panel?.classList.contains("is-open");
|
||||
qsa("[data-mobile-sub]").forEach((p) => p.classList.remove("is-open"));
|
||||
qsa(".mobile-cat").forEach((c) => c.classList.remove("is-expanded"));
|
||||
if (!open) {
|
||||
panel?.classList.add("is-open");
|
||||
cat?.classList.add("is-expanded");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Highlight current route in the mobile drawer
|
||||
const path = window.location.pathname.replace(/\/+$/, "") || "/";
|
||||
qsa(".mobile-nav a[href]").forEach((a) => {
|
||||
const href = (a.getAttribute("href") || "").replace(/\/+$/, "") || "/";
|
||||
if (href === path || (href !== "/" && path.startsWith(href))) {
|
||||
a.setAttribute("aria-current", "page");
|
||||
a.classList.add("is-active");
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
if (localStorage.getItem(SIDE_NAV_KEY) === "1") {
|
||||
setSideNavCollapsed(true);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
|
||||
sideNavToggle?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setSideNavCollapsed(!sideNav?.classList.contains("is-collapsed"));
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") {
|
||||
closeMega();
|
||||
closeDrawer();
|
||||
closeCartPreview();
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user