first deployment of kikomiko with url photos

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
shayan khoshrou
2026-10-19 19:10:57 +02:00
co-authored by Cursor
parent 8d98304137
commit ae3ed7c7b7
159 changed files with 59509 additions and 0 deletions
+316
View File
@@ -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("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;");
}
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();
}
});
}