import { formatPrice, initShadePicker } from "/js/buy.js?v=pdpFx1"; import { renderProductContent } from "/js/product-content.js?v=content5"; import { ensureEurRate, sellCompareFromItem, sellPriceFromItem, } from "/js/fx.js?v=fx1"; function moneyItem(product, variant) { const v = variant || {}; return { price: v.price ?? product?.price, compareAtPrice: v.compareAtPrice ?? product?.compareAtPrice, priceEur: v.priceEur ?? product?.priceEur, sourcePrice: v.sourcePrice ?? product?.sourcePrice, sourceCurrency: v.sourceCurrency ?? product?.sourceCurrency, }; } function escapeHtml(s) { return String(s ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """); } function displayTitle(p) { const variants = p.colorVariants; let title = p.name; if (variants && variants.length >= 2) { const shade = variants.find((v) => v.slug === p.slug)?.shade; if (shade) { const trimmed = p.name.trim(); if (trimmed.endsWith(shade)) title = trimmed.slice(0, -shade.length).trim(); } } return title; } function uniqueImages(list) { const out = []; const seen = new Set(); for (const src of list || []) { if (typeof src !== "string" || !src || seen.has(src)) continue; seen.add(src); out.push(src); } return out; } function variantGalleryImages(v) { if (!v) return []; if (Array.isArray(v.images) && v.images.length) { return uniqueImages(v.images); } return v.heroImage ? [v.heroImage] : []; } /** Prefer shade gallery; fill from family images so PDP always looks like local multi-photo layout. */ function productGalleryImages(product, variant) { const variantImgs = variantGalleryImages(variant); const familyImgs = uniqueImages([ ...(product?.heroImage ? [product.heroImage] : []), ...((product?.images || []).filter((x) => typeof x === "string" && x)), ]); if (variantImgs.length >= 2) return variantImgs; return uniqueImages([...variantImgs, ...familyImgs]); } function inStock(p) { const variants = p.colorVariants; if (variants && variants.length > 1) { return variants.some((v) => Number(v.stock) > 0); } return Number(p.stock) > 0; } function activeVariant(p) { const variants = p.colorVariants || []; const wanted = new URLSearchParams(location.search).get('shade'); return variants.find(v=>wanted && v.shade===wanted) || variants.find((v) => v.slug === productSlugFromPath()) || variants.find(v=>v.id===p.defaultVariantId) || variants.find((v) => v.slug === p.slug) || variants[0] || null; } function shadePickerHtml(p) { const variants = p.colorVariants || []; if (variants.length < 2) return ""; const active = activeVariant(p); return `

رنگ: ${escapeHtml(active?.shade || "")}

${variants .map((v) => { const isActive = v.id === active?.id; const bg = v.heroImage ? `background-image:url('${escapeHtml(v.heroImage)}')` : ""; const variantTitle = displayTitle({ ...p, slug: v.slug, name: v.name || p.name }); const imgs = productGalleryImages(p, v); const m = moneyItem(p, v); const sell = sellPriceFromItem(m); const sellCompare = sellCompareFromItem(m); return ``; }) .join("")}
`; } /** Main image for buy column; remaining shots go in the content gallery. */ function splitProductImages(p, v) { const images = productGalleryImages(p, v); const main = v?.heroImage || p.heroImage || images[0] || ""; const rest = []; let skippedMain = false; for (const src of images) { if (!skippedMain && main && src === main) { skippedMain = true; continue; } rest.push(src); } return { main, rest, images }; } function buyPanelHtml(p) { const title = displayTitle(p); const v = activeVariant(p); const money = moneyItem(p, v); const price = sellPriceFromItem(money); const compare = sellCompareFromItem(money); const discount = compare && compare > price ? Math.round(((compare - price) / compare) * 100) : null; const stock = v?.stock ?? p.stock ?? 0; const low = p.lowStockThreshold ?? 5; const { main: hero } = splitProductImages(p, v); const stocked = Number(stock) > 0; const heroAlt = (v && (v.heroImageAlt || v.nameFa || v.name)) || p.heroImageAlt || title || ""; let stockMsg = ""; if (!stocked) stockMsg = "ناموجود"; else if (stock <= low) stockMsg = `موجودی محدود ${stock}`; const heroBlock = hero ? `
${escapeHtml(heroAlt)}
` : ``; return `

انحصاری

${escapeHtml(title)}

${p.tagline ? `

${escapeHtml(p.tagline)}

` : ""}
${escapeHtml(formatPrice(price))} ${ discount != null ? `${escapeHtml(formatPrice(compare))}−${discount}%` : "" }

${escapeHtml(stockMsg)}

${heroBlock} ${shadePickerHtml(p)}
`; } function galleryImageAlt(src, index, richAlts, fallbackAlt) { const fromRich = richAlts.find((x) => x && x.url === src)?.alt || (typeof richAlts[index] === "string" ? richAlts[index] : richAlts[index]?.alt); return fromRich || (index === 0 ? fallbackAlt : `${fallbackAlt} – تصویر ${index + 1}`); } /** Extra photos (hero lives under the buy CTAs). Stacked full-width. */ function galleryHtml(p) { const v = activeVariant(p); const { rest: images } = splitProductImages(p, v); if (!images.length) return ""; const richAlts = (v && v.imageAlts) || p.imageAlts || []; const fallbackAlt = (v && (v.heroImageAlt || v.nameFa || v.name)) || p.heroImageAlt || p.nameFa || p.name || ""; return images .map((src, i) => { const alt = galleryImageAlt(src, i + 1, richAlts, fallbackAlt); return ``; }) .join(""); } function productSlugFromPath() { const m = location.pathname.match(/\/products\/([^/]+)\/?$/); return m ? decodeURIComponent(m[1]) : ""; } /** * Shared PDP for every catalog product (and any future import/export). * Routing always serves products/index.html; this hydrates from catalog.dedupe2.json by slug. */ export async function initProductPage() { if (!document.body.hasAttribute("data-product-page")) return; const slug = productSlugFromPath(); const root = document.getElementById("product-root"); const heroWrap = document.querySelector("[data-pdp-hero-wrap]"); const gallery = document.querySelector("[data-variant-gallery]"); const desc = document.querySelector("[data-pdp-description]"); if (!slug || !root) return; await ensureEurRate(); let products = []; try { const res = await fetch("/data/catalog.dedupe2.json"); if (!res.ok) throw new Error("catalog"); const data = await res.json(); products = data.products || []; } catch { root.innerHTML = `

بارگذاری محصول ناموفق بود.

`; return; } const p = products.find((x) => x.slug === slug || x.colorVariants?.some(v=>v.slug===slug)); if (!p) { root.innerHTML = `

محصول یافت نشد.

بازگشت به فروشگاه

`; return; } const title = displayTitle(p); document.title = `${title} | کیکومیکو`; const metaDesc = document.querySelector('meta[name="description"]'); if (metaDesc) { metaDesc.setAttribute( "content", p.seoDescription || p.tagline || p.description || document.title ); } if (gallery) { gallery.innerHTML = galleryHtml(p); if (heroWrap) heroWrap.hidden = !gallery.innerHTML; } const shadesSlot = document.querySelector("[data-pdp-shades]"); if (shadesSlot) { shadesSlot.innerHTML = ""; shadesSlot.hidden = true; } root.innerHTML = buyPanelHtml(p); initShadePicker(); renderProductContent(desc, p, activeVariant(p) || {}); root.addEventListener("click", (event) => { const button = event.target.closest("[data-shade-select]"); if (!button) return; const variant = p.colorVariants?.find((v) => v.id === button.dataset.id); if (variant) queueMicrotask(() => renderProductContent(desc, p, variant)); }); }