/* Add custom JS code below */ (function () { "use strict"; // Language detection: relies solely on / dir attribute var LANG = { detect: function () { var htmlLang = ( document.documentElement.getAttribute("lang") || "" ).toLowerCase(); if (htmlLang.indexOf("en") === 0) return "en"; if (htmlLang.indexOf("ar") === 0) return "ar"; var dir = ( document.documentElement.getAttribute("dir") || "" ).toLowerCase(); if (dir === "ltr") return "en"; return "ar"; }, isAr: function () { return this.detect() === "ar"; }, t: function (arText, enText) { return this.isAr() ? arText : enText; }, }; // ==================== SHARED API HEADERS ==================== // Every Salla API call MUST carry the current page language, otherwise // the API silently falls back to Arabic (this is what was causing the // "data always comes in Arabic" issue). extra lets a specific call add // headers on top of the shared base without duplicating this block. function buildApiHeaders(extra) { var lang = LANG.detect(); // "ar" or "en" var headers = { accept: "application/json, text/plain, */*", currency: "SAR", "accept-language": lang, lang: lang, origin: window.location.origin, "store-identifier": "1761887103", "x-requested-with": "XMLHttpRequest", }; if (extra) { for (var k in extra) headers[k] = extra[k]; } return headers; } var CONFIG = { MAX_TRIES: 50, CHECK_INTERVAL: 500, INJECTED_CLASS: "roani-card-opt-injected", VARIANT_CLASS: "roani-variants-injected", BUNDLES_CLASS: "roani-bundles-injected", USP_CLASS: "roani-usp-injected", TITLE_ROW_CLASS: "roani-title-row-injected", PRODUCTS_API: "https://api.salla.dev/store/v1/products/options", COMMENTS_API: "https://api.salla.dev/store/v1/comments", RATING_SUMMARY_API: "https://api.salla.dev/store/v1/rating/summary/", STORE_ID: "210732775", BUNDLE_IDS: ["1557700897", "1143856518"], // Desired display order for "روائح مشابهة" (right-to-left reading order): // سيرينا -> سولا -> قلاسيلا -> سوفيا -> قستيا VARIANT_ORDER: [ "1344233220", "1756892624", "1528707975", "1474857014", "769327947", ], VIDEO_SECTION_SELECTOR: "#slider_videos-", BADGES: [ { icon: "🌿", text_ar: "عضوية وطبيعية", text_en: "Organic & Natural" }, { icon: "🕯️", text_ar: "فتيل 100% قطن", text_en: "100% Cotton Wick" }, ], }; // NOTE: description / notes / feeling below are custom marketing copy // that does NOT exist anywhere in Salla's product API response (the API // only returns "description" as a raw HTML/image blob, no "notes" or // "feeling" fields at all). So these three stay hardcoded on purpose — // sending accept-language won't ever produce them. Only `name` and // `subtitle` are real Salla fields, and those now come localized // straight from the API once the language headers are sent, so the // BUNDLE_NAMES_EN / BUNDLE_SUBTITLES_EN hacks below are kept only as a // safety-net fallback in case a specific bundle response is missing one. var BUNDLE_NAMES_EN = { 1557700897: "For All Your Moments", 1143856518: "For Your Important Moment", }; var BUNDLE_SUBTITLES_EN = { 1557700897: "A harmony that follows you from first light to the calm of night", 1143856518: "A warm, gentle scent that flows with the calm of the moment", }; var STATIC_DATA = [ { id: 1528707975, name_ar: "قلاسيلا", name_en: "Glacella", description: "لحظة هدوء تحت أحضان الطبيعة.", description_en: "A calm moment embraced by nature.", notes: ["خشب الصندل","العسل", "الياسمين", "زهور فاكهية"], notes_en: ["Sandalwood", "Honey", "Jasmine", "Fruity Blooms"], feeling: "السكون في حديقة عند وقت العصر", feeling_en: "Stillness in a garden at dawn", }, { id: 1344233220, name_ar: "سيرينا", name_en: "Serena", description: "حلم وردي يوقظ صباحك بلطف.", description_en: "A rosy dream that gently wakes your morning.", notes: ["البرغموت", "البنفسج", "الباتشولي", "خشب الصندل"], notes_en: ["Bergamot", "Apple", "Berries", "Sandalwood"], feeling: "بسمة فجر بعد ليلة طويلة", feeling_en: "A dawn smile after a long night", }, { id: 1474857014, name_ar: "سوفيا", name_en: "Sovea", description: "دفء يجمع القلوب عند الغروب.", description_en: "Warmth that brings hearts together at sunset.", notes: ["الفلفل الوردي", "اللافندر", "المسك", "الفانيلا"], notes_en: ["Pink Pepper", "Lavender", "Musk", "Vanilla"], feeling: "دفء وقت الغروب", feeling_en: "Warmth at sunset", }, { id: 1756892624, name_ar: "سولا", name_en: "Sola", description: "إشراقة تملأ صباحك بالطاقة.", description_en: "A radiance that fills your morning with energy.", notes: ["خشب الصندل", "كاكاو", "ياسمين"], notes_en: ["Sandalwood", "Cocoa", "Jasmine"], feeling: "شمس الصباح على شرفة دافئة", feeling_en: "Morning sun on a warm balcony", }, { id: 769327947, name_ar: "قستيا", name_en: "Gustea", description: "سكون ناعم في نهاية اليوم.", description_en: "A gentle stillness at the end of the day.", notes: ["الجلد", "المسك", "الفريزيا", "لمسة خشبية"], notes_en: ["Freesia", "Musk", "Leather", "Wooden Touch"], feeling: "نهاية يوم طويل بهدوء تام", feeling_en: "The calm end of a long day", }, ]; function getStaticData(productId) { for (var i = 0; i < STATIC_DATA.length; i++) { if (String(STATIC_DATA[i].id) === String(productId)) return STATIC_DATA[i]; } return null; } // Sorts products by a fixed list of ids. Any product whose id isn't in // orderIds is left out of the sort and appended at the end (in its // original relative order), so new/unlisted products never disappear — // they just land last instead of breaking the layout. function sortByIdOrder(products, orderIds) { var orderIndex = {}; orderIds.forEach(function (id, idx) { orderIndex[String(id)] = idx; }); var known = []; var unknown = []; products.forEach(function (p) { if (orderIndex.hasOwnProperty(String(p.id))) known.push(p); else unknown.push(p); }); known.sort(function (a, b) { return orderIndex[String(a.id)] - orderIndex[String(b.id)]; }); return known.concat(unknown); } function isArabicText(str) { return /[\u0600-\u06FF]/.test(str); } function isLatinText(str) { return /[A-Za-z]/.test(str); } // Splits "English - عربي" style names and returns the part matching current language // Kept as a fallback for the rare case the API still returns a mixed string. function cleanProductName(rawName) { if (!rawName) return ""; var name = String(rawName).trim(); var wantArabic = LANG.isAr(); var separators = [" - ", " – ", " — ", "-", "–", "—"]; for (var i = 0; i < separators.length; i++) { var sep = separators[i]; var idx = name.indexOf(sep); if (idx !== -1) { var parts = name.split(sep); var j; for (j = 0; j < parts.length; j++) { var part = parts[j].trim(); if (!part) continue; if (wantArabic && isArabicText(part)) return part; if (!wantArabic && isLatinText(part) && !isArabicText(part)) return part; } return wantArabic ? parts[parts.length - 1].trim() : parts[0].trim(); } } return name; } function getDisplayName(product) { // 1) Trust the API first — with the lang header sent, product.name // should already be in the right language. if (product && product.name) { if (!isArabicText(product.name) === !LANG.isAr()) { // language of the string doesn't match what we expect -> mixed/legacy string, clean it return cleanProductName(product.name); } return product.name.trim(); } // 2) Fallback to static / hardcoded bundle names if the API gave nothing usable var staticData = getStaticData(product.id); if (staticData) return LANG.isAr() ? staticData.name_ar : staticData.name_en; if (!LANG.isAr() && BUNDLE_NAMES_EN[String(product.id)]) return BUNDLE_NAMES_EN[String(product.id)]; return cleanProductName(product.name); } function getDisplaySubtitle(product) { // Trust the API's localized subtitle first, fall back to hardcoded EN copy. if (product && product.subtitle) return product.subtitle; if (!LANG.isAr() && BUNDLE_SUBTITLES_EN[String(product.id)]) { return BUNDLE_SUBTITLES_EN[String(product.id)]; } return product.subtitle; } var state = { isInjected: false, uspInjected: false, titleRowInjected: false, variantInjected: false, bundlesInjected: false, tries: 0, uspTries: 0, titleRowTries: 0, variantTries: 0, bundlesTries: 0, interval: null, uspInterval: null, titleRowInterval: null, variantInterval: null, bundlesInterval: null, currentProductId: null, // Whether the page we're currently on IS a bundle's own product page. // Computed once (on first successful injectVariants() run) and kept // fixed for the lifetime of the page — switching between bundles via // the cards below never changes what page we're physically on. isBundlePage: null, variantsData: [], currentVariantIds: [], }; // ==================== INJECT (UI fixes) ==================== function inject() { if (document.querySelector("." + CONFIG.INJECTED_CLASS)) return; var thumbSlides = document.querySelectorAll( ".s-slider-thumbs .swiper-slide", ); var wishlistBar = document.querySelector(".main-content .shadow-sm"); if (!thumbSlides.length || !wishlistBar) return; var marker = document.createElement("span"); marker.className = CONFIG.INJECTED_CLASS; marker.style.display = "none"; document.body.appendChild(marker); thumbSlides.forEach(function (slide) { slide.style.cssText += "aspect-ratio: 1 / 1 !important; height: auto !important;"; }); var styleEl = document.createElement("style"); styleEl.textContent = [ ".s-slider-thumbs .swiper-slide { aspect-ratio: 1 / 1 !important; }", ".s-slider-thumbs .swiper-slide .lazy__bg { background-size: cover !important; background-position: center !important; }", ].join(" "); document.head.appendChild(styleEl); wishlistBar.style.display = "none"; cleanMainProductTitle(); state.isInjected = true; } function cleanMainProductTitle() { var titleEl = document.querySelector(".product-title"); if (!titleEl) return; var currentId = state.currentProductId || getCurrentProductId(); var staticData = currentId ? getStaticData(currentId) : null; if (staticData) { titleEl.textContent = LANG.isAr() ? staticData.name_ar : staticData.name_en; return; } if (!LANG.isAr() && currentId && BUNDLE_NAMES_EN[String(currentId)]) { titleEl.textContent = BUNDLE_NAMES_EN[String(currentId)]; return; } var cleaned = cleanProductName(titleEl.textContent); if (cleaned && cleaned !== titleEl.textContent.trim()) { titleEl.textContent = cleaned; } } // ==================== USP BADGES ==================== function injectUSP() { var existing = document.querySelector("." + CONFIG.USP_CLASS); if (existing) existing.remove(); var titleEl = document.querySelector(".product-title"); if (!titleEl) return; var row = document.createElement("div"); row.className = CONFIG.USP_CLASS; row.style.cssText = "display:flex;gap:10px;flex-wrap:wrap;margin:10px 0 14px;direction:" + (LANG.isAr() ? "rtl" : "ltr") + ";"; CONFIG.BADGES.forEach(function (badge) { var span = document.createElement("span"); span.style.cssText = "display:inline-flex;align-items:center;gap:7px;padding:5px 14px;border-radius:999px;border:0.5px solid #dccfbc;background:#ffffff;color:#4a403a;font-size:13px;font-weight:500;line-height:1.4;"; var label = LANG.isAr() ? badge.text_ar : badge.text_en; span.innerHTML = "" + badge.icon + "" + label + ""; row.appendChild(span); }); titleEl.insertAdjacentElement("afterend", row); state.uspInjected = true; } // ==================== TITLE ROW ==================== function injectTitleRow() { if (document.querySelector("." + CONFIG.TITLE_ROW_CLASS)) return; var titleEl = document.querySelector(".product-title"); if (!titleEl) return; var staticWrap = document.querySelector(".main-content-static"); if (!staticWrap) return; var shareBar = staticWrap.querySelector(".shadow-sm"); if (!shareBar) return; shareBar.className = "roani-share-bar"; shareBar.style.cssText = ""; var titleParent = titleEl.parentNode; var wrapper = document.createElement("div"); wrapper.className = CONFIG.TITLE_ROW_CLASS; wrapper.style.cssText = [ "display:flex", "align-items:center", "justify-content:space-between", "gap:12px", "direction:" + (LANG.isAr() ? "rtl" : "ltr"), "width:100%", ].join(";"); var titleWrap = document.createElement("div"); titleWrap.style.cssText = "flex:1;min-width:0;"; titleWrap.appendChild(titleEl.cloneNode(true)); wrapper.appendChild(titleWrap); wrapper.appendChild(shareBar); titleParent.replaceChild(wrapper, titleEl); state.titleRowInjected = true; } // ==================== HELPERS ==================== function getCurrentProductId() { var comp = document.querySelector( "show-home-component[component-path='saji-photo-gallery']", ); if (comp) return comp.getAttribute("current-prod-id"); var form = document.querySelector("form.product-form"); if (!form) return null; var addBtn = form.querySelector("[product-id]"); return addBtn ? addBtn.getAttribute("product-id") : null; } // Best-effort resolver for a product's own page URL, used when we need // to navigate (full page load) to a bundle instead of updating in place. function getProductUrl(p) { if (!p) return null; if (p.url) return p.url; if (p.urls && p.urls.customer) return p.urls.customer; if (p.urls && p.urls.customer_url) return p.urls.customer_url; return null; } function isEmptyParagraphEl(el) { if (!el || el.nodeType !== 1 || el.tagName !== "P") return false; var html = el.innerHTML.trim().toLowerCase(); var text = el.textContent.trim(); return (html === "
" || html === "" || html === "
") && text === ""; } function removeConsecutiveEmptyParagraphsInBody(container) { if (!container) return; var children = Array.prototype.slice.call(container.children); var previousWasEmpty = false; for (var i = 0; i < children.length; i++) { var el = children[i]; var currentIsEmpty = isEmptyParagraphEl(el); if (currentIsEmpty && previousWasEmpty) { el.remove(); } else { previousWasEmpty = currentIsEmpty; } } } function fetchVariantsData(ids, callback) { if (!ids.length) return; var params = ids.map(function (id) { return "selected[]=" + id; }); params.push("with[]=images"); var url = CONFIG.PRODUCTS_API + "?" + params.join("&"); fetch(url, { method: "GET", headers: buildApiHeaders(), }) .then(function (res) { return res.json(); }) .then(function (json) { if (json && json.data) callback(json.data); }) .catch(function (err) { console.error("Variants fetch error:", err); }); } // ==================== SMOOTH SCROLL ==================== function smoothScrollToTop() { var target = document.querySelector(".s-slider-container") || document.querySelector(".product-entry") || document.querySelector(".main-content"); if (target) { var headerOffset = 80; var elementPosition = target.getBoundingClientRect().top; var offsetPosition = elementPosition + window.pageYOffset - headerOffset; window.scrollTo({ top: offsetPosition, behavior: "smooth" }); } else { window.scrollTo({ top: 0, behavior: "smooth" }); } } // ==================== PRICE BLOCK ==================== function readCurrentPrices() { var salePriceEl = document.querySelector(".total-price-single"); var beforePriceEl = document.querySelector(".before-price-single"); var saleText = salePriceEl ? salePriceEl.textContent.trim().replace(/[^\d.]/g, "") : ""; var beforeText = beforePriceEl ? beforePriceEl.textContent.trim().replace(/[^\d.]/g, "") : ""; return { sale: saleText, before: beforeText }; } function buildPriceBlock(salePrice, regularPrice) { if (!salePrice || salePrice === "" || isNaN(parseFloat(salePrice))) return false; var existing = document.querySelector(".roani-price-block"); if (existing) existing.remove(); var salePriceEl = document.querySelector(".total-price-single"); var existingPriceWrap = salePriceEl ? salePriceEl.closest(".flex.flex-wrap") : null; if (!existingPriceWrap) return false; var currencyLabel = LANG.isAr() ? "ر.س" : "SAR"; var saveWord = LANG.isAr() ? "وفر" : "Save"; var offWord = LANG.isAr() ? "خصم" : "Off"; var taxWord = LANG.isAr() ? "شامل الضريبة" : "Tax Included"; var saleNum = parseFloat(salePrice); var regularNum = regularPrice ? parseFloat(regularPrice) : null; var saveAmount = regularNum && saleNum && regularNum > saleNum ? regularNum - saleNum : null; var discountPct = saveAmount ? Math.round((saveAmount / regularNum) * 100) : null; var newBlock = document.createElement("div"); newBlock.className = "roani-price-block"; newBlock.innerHTML = '' + salePrice + " " + currencyLabel + "" + (regularNum && regularNum !== saleNum ? '' + regularPrice + " " + currencyLabel + "" : "") + (saveAmount ? '' + saveWord + " " + saveAmount + " • " + offWord + " " + discountPct + "%" : "") + '' + taxWord + ""; existingPriceWrap.style.display = "none"; var taxEl = document.querySelector("small.text-red-400"); if (taxEl) taxEl.style.display = "none"; existingPriceWrap.parentNode.insertBefore(newBlock, existingPriceWrap); return true; } // ==================== STATIC INFO ==================== function buildStaticInfoBlock(staticData) { var existing = document.querySelector(".roani-static-info"); if (existing) existing.remove(); if (!staticData) return; var starsEl = document.querySelector( "form.product-form salla-rating-stars", ); var ratingRow = starsEl ? starsEl.closest(".flex.items-center.mb-4") : null; var container = ratingRow ? ratingRow.parentElement : document.querySelector("form.product-form .flex.flex-col"); if (!container) return; var insertAfter = ratingRow || (container.children.length > 0 ? container.children[0] : null); var isAr = LANG.isAr(); var block = document.createElement("div"); block.className = "roani-static-info"; var descEl = document.createElement("p"); descEl.className = "roani-short-desc"; descEl.textContent = isAr ? staticData.description : staticData.description_en; block.appendChild(descEl); var rows = [ { label: isAr ? "المكونات" : "Notes", value: (isAr ? staticData.notes : staticData.notes_en).join( isAr ? "، " : ", ", ), }, { label: isAr ? "الإحساس" : "Feeling", value: isAr ? staticData.feeling : staticData.feeling_en, }, ]; var table = document.createElement("div"); table.className = "roani-info-table"; rows.forEach(function (row) { var rowEl = document.createElement("div"); rowEl.className = "roani-info-row"; var labelEl = document.createElement("span"); labelEl.className = "roani-info-label"; labelEl.textContent = row.label; var valueEl = document.createElement("span"); valueEl.className = "roani-info-value"; valueEl.textContent = row.value; rowEl.appendChild(labelEl); rowEl.appendChild(valueEl); table.appendChild(rowEl); }); block.appendChild(table); if (insertAfter && insertAfter.nextSibling) { container.insertBefore(block, insertAfter.nextSibling); } else if (insertAfter) { container.appendChild(block); } else { container.prepend(block); } } // ==================== SALLA CART ADD ==================== function sallaCartAdd(productId, btnEl) { return window.salla.cart .addItem({ id: Number(productId), quantity: 1, }) .then(function (res) { btnEl.classList.remove("is-loading"); btnEl.classList.add("is-success"); setTimeout(function () { btnEl.classList.remove("is-success"); btnEl.disabled = false; }, 2000); return res; }) .catch(function (err) { console.error("salla.cart.addItem error:", err); btnEl.classList.remove("is-loading"); btnEl.classList.add("is-error"); btnEl.disabled = false; setTimeout(function () { btnEl.classList.remove("is-error"); }, 1800); throw err; }); } // ==================== FETCH COMMENTS ==================== function fetchComments(productId, callback) { var url = CONFIG.COMMENTS_API + "?per_page=5&page=1&type=products&ids[]=" + productId; fetch(url, { method: "GET", headers: buildApiHeaders(), }) .then(function (res) { return res.json(); }) .then(function (json) { if (json && json.data) callback(json.data, json.pagination); }) .catch(function (err) { console.error("Comments fetch error:", err); }); } // ==================== RATING SUMMARY ==================== function fetchRatingSummary(productId, callback) { var url = CONFIG.RATING_SUMMARY_API + productId; fetch(url, { method: "GET", headers: buildApiHeaders(), }) .then(function (res) { return res.json(); }) .then(function (json) { if (json && json.data) callback(json.data); }) .catch(function (err) { console.error("Rating summary fetch error:", err); }); } function updateRatingSummary(ratingData) { var starsEl = document.querySelector( "form.product-form salla-rating-stars", ); if (!starsEl) return; if (ratingData.rating != null) { starsEl.setAttribute("value", ratingData.rating); var starSpans = starsEl.querySelectorAll(".s-rating-stars-btn-star"); var filled = Math.round(ratingData.rating); starSpans.forEach(function (star, idx) { if (idx < filled) star.classList.add("s-rating-stars-selected"); else star.classList.remove("s-rating-stars-selected"); }); } var ratingCountEl = starsEl.parentElement ? starsEl.parentElement.querySelector("strong") : null; if (ratingCountEl && ratingData.count != null) { var totalDisplay = LANG.isAr() ? toArabicNumerals(ratingData.count) : ratingData.count; var suffix = LANG.isAr() ? "تقييم" : "ratings"; ratingCountEl.textContent = "(" + totalDisplay + ") " + suffix; } } function updateComments(comments, pagination) { var sallaComments = document.querySelector("salla-comments"); if (!sallaComments) return; var countLabel = sallaComments.querySelector(".s-comments-count-label"); if (countLabel && pagination) { var totalDisplay = LANG.isAr() ? toArabicNumerals(pagination.total) : pagination.total; var suffix = LANG.isAr() ? "تعليق" : "comments"; countLabel.innerHTML = " " + totalDisplay + " " + suffix; } var firstItem = sallaComments.querySelector("salla-comment-item"); if (!firstItem) return; var itemsParent = firstItem.parentNode; var existingItems = itemsParent.querySelectorAll("salla-comment-item"); existingItems.forEach(function (el) { el.remove(); }); comments.forEach(function (comment) { var item = buildCommentItem(comment); itemsParent.appendChild(item); }); } function buildCommentItem(comment) { var wrapper = document.createElement("salla-comment-item"); wrapper.className = "s-comments-item hydrated animated"; wrapper.style.cssText = "opacity: 1; transform: translateY(0px);"; function buildStars(rating) { var html = '
'; for (var i = 1; i <= 5; i++) { var selected = i <= rating ? " s-rating-stars-selected" : ""; html += '' + '' + '' + ""; } html += "
"; return html; } function formatDate(createdAt) { try { var d = new Date(createdAt.date.replace(" ", "T")); var day = d.getDate(); var month = d.getMonth() + 1; var year = d.getFullYear(); var hours = d.getHours(); var minutes = d.getMinutes(); return ( day + "/" + month + "/" + year + ' - ' + hours + ":" + (minutes < 10 ? "0" : "") + minutes + "" ); } catch (e) { return ""; } } var imagesHtml = ""; if (comment.images && comment.images.length) { comment.images.forEach(function (imgUrl) { imagesHtml += ''; }); } var checkSvg = ''; var hasOrderText = LANG.isAr() ? "قام بالشراء , " : "Verified purchase, "; var ratedText = LANG.isAr() ? "تم التقييم" : "Rated"; wrapper.innerHTML = [ '
', '
', '
', '' +
        (comment.name || ', "
", '
', '
', '", '

' + formatDate(comment.created_at) + "

", '' + buildStars(comment.rating || comment.stars || 5) + "", "
", '
', '
', "

" + (comment.content || comment.text || "") + "

", "
", '
' + imagesHtml + "
", "
", "
", "
", "
", ].join(""); // salla-rating-stars hydrates asynchronously and injects its own // interactive stars wrapper on top of ours — strip it, once now and // a few more times shortly after in case hydration hasn't run yet. var starsEl = wrapper.querySelector( "salla-rating-stars.s-comments-item-stars", ); stripInteractiveStars(starsEl); var starsCleanTries = 0; var starsCleanInterval = setInterval(function () { starsCleanTries++; stripInteractiveStars(starsEl); if (starsCleanTries >= 10) clearInterval(starsCleanInterval); }, 200); return wrapper; } // The custom element hydrates itself and injects its // own interactive "pick a rating" widget (buttons + hidden input) as a // second ".s-rating-stars-wrapper" alongside the display-only stars we // build ourselves. That second wrapper is never the one with our // "-mini"/"-selected" star spans, so we can safely detect and remove it. function stripInteractiveStars(starsEl) { if (!starsEl) return; var wrappers = starsEl.querySelectorAll(".s-rating-stars-wrapper"); wrappers.forEach(function (w) { if (!w.querySelector(".s-rating-stars-mini")) { w.remove(); } }); } function toArabicNumerals(num) { return String(num).replace(/\d/g, function (d) { return "٠١٢٣٤٥٦٧٨٩"[d]; }); } function refreshRatingsAndComments(productId) { fetchComments(productId, function (comments, pagination) { if (String(state.currentProductId) !== String(productId)) return; updateComments(comments, pagination); }); fetchRatingSummary(productId, function (ratingData) { if (String(state.currentProductId) !== String(productId)) return; updateRatingSummary(ratingData); }); } // ==================== VIDEO SECTION ("ماذا يأتي مع شمعة ...") ==================== // The API's `images` array sometimes contains a video entry mixed in with // normal images: { type: "video", url: , video_url: }. // We scan for that entry, tell it apart from real images via type==="video", // and swap it into the video slider section, updating the section title too. function findVideoInImages(images) { if (!images || !images.length) return null; for (var i = 0; i < images.length; i++) { if (images[i] && images[i].type === "video" && images[i].video_url) { return images[i]; } } return null; } function extractYouTubeId(url) { if (!url) return null; var match = url.match( /(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([A-Za-z0-9_-]{6,})/, ); return match ? match[1] : null; } function buildVideoSectionTitle(name) { return LANG.isAr() ? "ماذا يأتي مع شمعة " + name : "What Comes With " + name + " Candle"; } function updateVideoSection(product) { var section = document.querySelector(CONFIG.VIDEO_SECTION_SELECTOR); if (!section) return; var displayName = getDisplayName(product); var titleEl = section.querySelector(".saji-main-title"); if (titleEl) titleEl.textContent = buildVideoSectionTitle(displayName); var slideItem = section.querySelector(".slider_videos-item"); if (!slideItem) return; var videoData = findVideoInImages(product.images); if (!videoData) { // No video for this product: hide the whole section rather than show stale media. section.style.display = "none"; return; } section.style.display = ""; var ytId = extractYouTubeId(videoData.video_url); var existingVideoEl = slideItem.querySelector("video"); var existingIframeEl = slideItem.querySelector("iframe.roani-video-embed"); if (ytId) { // YouTube link: a native