/* ============================================================ GREY MATTERS — STORE CUSTOM JS (Cleaned & Fixed) ============================================================ Table of Contents: 1. Section Load Animation (Intersection Observer) 2. Fixed Banner Image Replacement (placeholder map) 3. Prevent Click on Empty Banner Links 4. Add LinkedIn Icon to Social Links List 5. WhatsApp Floating Button 6. WhatsApp Number Auto-Update (footer contact link) 7. "Our Clients" Logo Carousel 8. "Our Partners" Logo Carousel 9. Homepage Slider Speed / Autoplay Tweak 10. Concrete Technology Section — Download Link Guard below if needed. ============================================================ */ /* ============================================================ 1. SECTION LOAD ANIMATION Fades + slides each
into view once it scrolls into the viewport. Works with .before-animate / .animate-section classes defined in the CSS file. ============================================================ */ document.addEventListener("DOMContentLoaded", function () { const sections = document.querySelectorAll("section"); const sectionObserver = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add("animate-section"); sectionObserver.unobserve(entry.target); } }); }, { threshold: 0.15 } ); sections.forEach((section) => { section.classList.add("before-animate"); sectionObserver.observe(section); }); }); /* ============================================================ 2. FIXED BANNER IMAGE REPLACEMENT Swaps the inside specific fixed-banner sections by their position on the page (1st section, 3rd section, etc). Add entries to `replacements` as: sectionNumber: "new-url". Currently empty — inactive until entries are added. ============================================================ */ window.addEventListener("load", function () { const replacements = { // 1: "https://example.com/banner1.png", // 3: "https://example.com/banner2.png", }; if (Object.keys(replacements).length === 0) return; const sections = document.querySelectorAll("section"); sections.forEach((section, idx) => { const sectionNumber = idx + 1; if (!replacements[sectionNumber]) return; const img = section.querySelector('img[alt="fixed banner"]'); if (img) { const newSrc = replacements[sectionNumber]; img.src = newSrc; img.setAttribute("data-src", newSrc); // keep lazy-load attr in sync } }); }); /* ============================================================ 3. PREVENT CLICK ON EMPTY BANNER LINKS Banner entries without a real href shouldn't navigate anywhere when clicked. NOTE: wrapped in DOMContentLoaded so .banner-entry elements are guaranteed to exist before we query for them (fixes a silent no-op if this script loads/executes before the DOM is ready, e.g. when placed in without `defer`). ============================================================ */ document.addEventListener("DOMContentLoaded", function () { document.querySelectorAll(".banner-entry").forEach((link) => { if (!link.getAttribute("href")) { link.addEventListener("click", function (e) { e.preventDefault(); }); } }); }); /* ============================================================ 4. ADD LINKEDIN ICON TO SOCIAL LINKS LIST Salla's social widget doesn't support LinkedIn natively, so we inject it manually and keep watching for the widget being re-rendered (SPA navigation). FIX: renders its list inside a shadow root, so we must query through el.shadowRoot (same pattern as the WhatsApp-number fix in section 6) instead of querying the light DOM, which never contains ".s-social-list". ============================================================ */ (function () { const LINKEDIN_URL = "https://www.linkedin.com/company/grey-matters/"; function addLinkedIn(el) { const root = el.shadowRoot || el; if (root.querySelector('a[title="linkedin"]')) return; // already added const list = root.querySelector(".s-social-list"); if (!list) return; const li = document.createElement("li"); li.className = "s-social-link"; li.innerHTML = '' + '' + '' + '' + '' + '' + ''; list.appendChild(li); } function scan() { document.querySelectorAll("salla-social").forEach(addLinkedIn); } document.addEventListener("DOMContentLoaded", scan); new MutationObserver(scan).observe(document.body, { childList: true, subtree: true }); })(); /* ============================================================ 5. WHATSAPP FLOATING BUTTON Injects a fixed WhatsApp chat button (styled via .wa-fab classes in the CSS file) with a slide-in appearance. ============================================================ */ (function () { const CONFIG = { phone: "447311896662", // number, no + and no spaces message: "Hi, I'd like to ask about...", // pre-filled message ("" to disable) label: "Chat with us on WhatsApp", showAfter: 300 // ms delay before slide-in appears }; function buildLink() { const base = "https://wa.me/" + CONFIG.phone; return CONFIG.message ? base + "?text=" + encodeURIComponent(CONFIG.message) : base; } function createWidget() { const a = document.createElement("a"); a.className = "wa-fab"; a.href = buildLink(); a.target = "_blank"; a.rel = "noopener"; a.setAttribute("aria-label", CONFIG.label); a.innerHTML = '" + '"; document.body.appendChild(a); // Slide-in effect after the page settles window.setTimeout(function () { a.classList.add("wa-fab--visible"); }, CONFIG.showAfter); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", createWidget); } else { createWidget(); } })(); /* ============================================================ 6. WHATSAPP NUMBER AUTO-UPDATE (footer contact link) Salla's widget hardcodes a WhatsApp number we don't control via the dashboard, so we rewrite the link here. Uses MutationObserver (not a limited-attempt loop) so it keeps working even if the widget re-renders later or the page navigates without a full reload. ============================================================ */ (function () { const NEW_NUMBER = "447311896662"; function updateLink(link) { const target = "https://wa.me/" + NEW_NUMBER; if (link.getAttribute("href") === target) return; // already correct link.setAttribute("href", target); const span = link.querySelector(".unicode"); if (span) span.textContent = "+" + NEW_NUMBER; } function scanAndUpdate() { const host = document.querySelector("salla-contacts"); let waLink = null; if (host && host.shadowRoot) { waLink = host.shadowRoot.querySelector('a[href^="https://wa.me/"]'); } else { waLink = document.querySelector('a[href^="https://wa.me/"]'); } if (waLink) updateLink(waLink); } // Check immediately in case the element already exists scanAndUpdate(); // Watch the whole page for the widget appearing / re-rendering new MutationObserver(scanAndUpdate).observe(document.documentElement, { childList: true, subtree: true }); // Also watch inside the widget's shadow DOM once it's available function watchShadowRoot() { const host = document.querySelector("salla-contacts"); if (host && host.shadowRoot) { new MutationObserver(scanAndUpdate).observe(host.shadowRoot, { childList: true, subtree: true }); } else { setTimeout(watchShadowRoot, 500); } } watchShadowRoot(); })(); /* ============================================================ 7. "OUR CLIENTS" LOGO CAROUSEL Builds an infinite-scroll logo strip and inserts it right after the anchor section below. Homepage only. Styled via .gm-clients-* classes in the CSS file. NOTE: if "Our Clients" isn't showing up on the homepage, check the console warning this now prints — it means ANCHOR_SELECTOR doesn't match anything in the current theme. ============================================================ */ (function () { // سرعة الحركة: كل ما رفعت الرقم، كل ما بقت الحركة أبطأ (بالثواني) const SCROLL_DURATION_SECONDS = 60; const ANCHOR_SELECTOR = "#best-offers-1-slider"; document.addEventListener("DOMContentLoaded", function () { const path = window.location.pathname; if (path !== "/" && path !== "") return; const clients = [ { src: "https://greymatters.ws/wp-content/uploads/2024/03/Abdullah-Abdin.jpg", title: "Abdullah Abdin" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Al-Khalij-Cement-1.jpg", title: "Al Khalij Cement" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Cemex-1.jpg", title: "Cemex" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/CIC-1.jpg", title: "CIC" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Conmix.jpg", title: "Conmix" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Cortec-1.jpg", title: "Cortec" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/DCP-Saudi-1.jpg", title: "DCP Saudi" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Doka-1.jpg", title: "Doka" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Dubai-Municipality.jpg", title: "Dubai Municipality" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Emix.jpg", title: "Emix" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Green-Concrete.jpg", title: "Green Concrete" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/KTI-1.jpg", title: "KTI" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Mapei-1.jpg", title: "Mapei" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Oryx-Mix-1.jpg", title: "Oryx Mix" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/RMB-Ready-Concrete-Manufac.jpg", title: "RMB Ready Concrete Manufac" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Saudi-Readymix-1.jpg", title: "Saudi Readymix" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Sika-1.jpg", title: "Sika" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/Transgulf.jpg", title: "Transgulf" }, { src: "https://greymatters.ws/wp-content/uploads/2024/03/United-Readymix.jpg", title: "United Readymix" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/1-2.png", title: "Client 1" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/2.png", title: "Client 2" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/3.png", title: "Client 3" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/5.png", title: "Client 5" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/6.png", title: "Client 6" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/7.png", title: "Client 7" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/8.png", title: "Client 8" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/9.png", title: "Client 9" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/10.png", title: "Client 10" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/11.png", title: "Client 11" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/12.png", title: "Client 12" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/13.png", title: "Client 13" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/14.png", title: "Client 14" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/15.png", title: "Client 15" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/16.png", title: "Client 16" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/17.png", title: "Client 17" }, { src: "https://greymatters.ws/wp-content/uploads/2025/06/18.png", title: "Client 18" } ]; function buildLogos(list) { return list.map(function (c) { const img = document.createElement("img"); img.src = c.src; img.alt = c.title || ""; img.title = c.title || ""; img.loading = "lazy"; img.onerror = function () { this.closest(".gm-client-logo").style.display = "none"; }; const wrap = document.createElement("div"); wrap.className = "gm-client-logo"; wrap.appendChild(img); return wrap; }); } const section = document.createElement("section"); section.className = "gm-clients-section"; section.innerHTML = '

Our Clients

' + '
' + '
'; const track = section.querySelector("#gm-clients-track"); buildLogos(clients).concat(buildLogos(clients)).forEach(function (el) { track.appendChild(el); }); // تحديد سرعة الحركة (يتغلب على أي مدة محددة في ملف الـ CSS) if (!document.getElementById("gm-clients-speed-style")) { const speedStyle = document.createElement("style"); speedStyle.id = "gm-clients-speed-style"; speedStyle.textContent = "#gm-clients-track{animation-duration:" + SCROLL_DURATION_SECONDS + "s !important;}"; document.head.appendChild(speedStyle); } function insertAfterStats() { if (document.querySelector(".gm-clients-section")) return true; // already inserted const anchorEl = document.querySelector(ANCHOR_SELECTOR); const anchorSection = anchorEl ? anchorEl.closest("section") : null; if (anchorSection && anchorSection.parentNode) { anchorSection.parentNode.insertBefore(section, anchorSection.nextSibling); return true; } return false; } if (!insertAfterStats()) { // Keep retrying for a while (handles late-rendered/dynamic sections) let attempts = 0; const interval = setInterval(function () { attempts++; if (insertAfterStats()) { clearInterval(interval); } else if (attempts > 40) { clearInterval(interval); console.warn( '[gm-clients-section] Gave up after 12s: no element matching "' + ANCHOR_SELECTOR + '" was found on this page, so the "Our Clients" carousel was never inserted. ' + "Update ANCHOR_SELECTOR in section 7 of the custom JS to an element that exists in this theme." ); } }, 300); // Also watch the DOM directly in case the section appears asynchronously const mo = new MutationObserver(function () { if (insertAfterStats()) mo.disconnect(); }); mo.observe(document.body, { childList: true, subtree: true }); } }); })(); /* ============================================================ 8. "OUR PARTNERS" LOGO CAROUSEL Same pattern as the Clients carousel above. Inserts right after the Clients section if present, otherwise right after the same anchor used in section 7. Homepage only. Styled via .gm-partners-* classes. ============================================================ */ (function () { const ANCHOR_SELECTOR = "#best-offers-1-slider"; document.addEventListener("DOMContentLoaded", function () { const path = window.location.pathname; if (path !== "/" && path !== "") return; const partners = [ { src: "https://i.ibb.co/v6j3k39q/afnor-group.jpg", title: "AFNOR Group" }, { src: "https://i.ibb.co/qqpgfpc/concrete-sustainibility-council.jpg", title: "Concrete Sustainability Council" }, { src: "https://i.ibb.co/Z1FMTsTd/LCA.jpg", title: "LCA" }, { src: "https://i.ibb.co/dJ0Ppxst/NRMCA.jpg", title: "NRMCA" } ]; function buildLogos(list) { return list.map(function (c) { const img = document.createElement("img"); img.src = c.src; img.alt = c.title || ""; img.title = c.title || ""; img.loading = "lazy"; img.onerror = function () { this.closest(".gm-partner-logo").style.display = "none"; }; const wrap = document.createElement("div"); wrap.className = "gm-partner-logo"; wrap.appendChild(img); return wrap; }); } const section = document.createElement("section"); section.className = "gm-partners-section"; section.innerHTML = '

Our Partners

' + '
' + '
'; const track = section.querySelector("#gm-partners-track"); buildLogos(partners).concat(buildLogos(partners)).forEach(function (el) { track.appendChild(el); }); function insertSection() { if (document.querySelector(".gm-partners-section")) return true; // already inserted const clientsSection = document.querySelector(".gm-clients-section"); const anchorEl = document.querySelector(ANCHOR_SELECTOR); const anchorSection = anchorEl ? anchorEl.closest("section") : null; if (clientsSection && clientsSection.parentNode) { clientsSection.parentNode.insertBefore(section, clientsSection.nextSibling); return true; } if (anchorSection && anchorSection.parentNode) { anchorSection.parentNode.insertBefore(section, anchorSection.nextSibling); return true; } return false; } if (!insertSection()) { // Keep retrying for a while (handles late-rendered/dynamic sections) let attempts = 0; const interval = setInterval(function () { attempts++; if (insertSection()) { clearInterval(interval); } else if (attempts > 40) { clearInterval(interval); console.warn( '[gm-partners-section] Gave up after 12s: no ".gm-clients-section" and no element ' + 'matching "' + ANCHOR_SELECTOR + '" were found, so the "Our Partners" carousel was never inserted.' ); } }, 300); // Also watch the DOM directly in case the section appears asynchronously const mo = new MutationObserver(function () { if (insertSection()) mo.disconnect(); }); mo.observe(document.body, { childList: true, subtree: true }); } }); })(); /* ============================================================ 9. HOMEPAGE SLIDER SPEED / AUTOPLAY TWEAK Slight delay to let the theme finish initializing Swiper, then adjust transition speed and autoplay delay. FIX: guard on params.autoplay existing before touching it — if autoplay isn't enabled on this slider from the Salla dashboard, params.autoplay is `false`, not an object, and the old code threw a console error trying to read .delay off it. ============================================================ */ document.addEventListener('DOMContentLoaded', function () { // ناخد وقت بسيط عشان نتأكد إن Swiper اتعمل له init من الثيم setTimeout(function () { var sliderEl = document.querySelector('.s-slider-container'); if (sliderEl && sliderEl.swiper && sliderEl.swiper.params.autoplay) { sliderEl.swiper.params.speed = 300; // سرعة الانتقال (كل ما الرقم أقل كل ما أسرع) sliderEl.swiper.params.autoplay.delay = 3000; // المدة بين كل سلايد sliderEl.swiper.autoplay.stop(); sliderEl.swiper.autoplay.start(); sliderEl.swiper.update(); } }, 800); }); /* ============================================================ 10. CONCRETE TECHNOLOGY SECTION — DOWNLOAD LINK GUARD Stops the download button from firing while it still points at the placeholder filename, and shows an inline notice for whoever is editing the page instead of failing silently for a real visitor. ============================================================ */ (function () { "use strict"; var section = document.getElementById("concrete-tech-section"); if (!section) return; var downloadLink = section.querySelector("#cts-download"); if (!downloadLink) return; var PLACEHOLDER_HREF = "concrete-technology-career-track.pdf"; downloadLink.addEventListener("click", function (event) { var href = downloadLink.getAttribute("href"); // If the real PDF hasn't been linked yet, stop the broken download // and let the editor know instead of failing silently for the visitor. if (href === PLACEHOLDER_HREF) { event.preventDefault(); console.warn( '[concrete-tech-section] The download button still points to the placeholder "' + PLACEHOLDER_HREF + '". Update its href to your uploaded PDF\'s real path or URL.' ); showInlineNotice(downloadLink, "لسه لازم تحط رابط ملف الـ PDF الحقيقي"); } }); function showInlineNotice(anchor, message) { var existing = anchor.parentElement.querySelector(".cts-notice"); if (existing) existing.remove(); var notice = document.createElement("span"); notice.className = "cts-notice"; notice.textContent = message; notice.style.cssText = "display:block;margin-top:8px;font-size:12px;color:#8c3b2e;"; anchor.insertAdjacentElement("afterend", notice); window.setTimeout(function () { notice.remove(); }, 4000); } })(); // الصفحة التعريفية (function () { "use strict"; var section = document.getElementById("concrete-tech-section"); if (!section) return; var downloadLink = section.querySelector("#cts-download"); if (!downloadLink) return; var PLACEHOLDER_HREF = "concrete-technology-career-track.pdf"; downloadLink.addEventListener("click", function (event) { var href = downloadLink.getAttribute("href"); // If the real PDF hasn't been linked yet, stop the broken download // and let the editor know instead of failing silently for the visitor. if (href === PLACEHOLDER_HREF) { event.preventDefault(); console.warn( '[concrete-tech-section] The download button still points to the placeholder "' + PLACEHOLDER_HREF + '". Update its href to your uploaded PDF\'s real path or URL.' ); showInlineNotice(downloadLink, "لسه لازم تحط رابط ملف الـ PDF الحقيقي"); } }); function showInlineNotice(anchor, message) { var existing = anchor.parentElement.querySelector(".cts-notice"); if (existing) existing.remove(); var notice = document.createElement("span"); notice.className = "cts-notice"; notice.textContent = message; notice.style.cssText = "display:block;margin-top:8px;font-size:12px;color:#8c3b2e;"; anchor.insertAdjacentElement("afterend", notice); window.setTimeout(function () { notice.remove(); }, 4000); } })();