1// Set to true to enable ?geo_debug=ID|SG query param for testing 2const GEO_BANNER_DEBUG = true; 3 4// Controls which banner directions are active. 5// showOnEnSgPage : show banner to ID visitors on EN-SG pages â redirect to ID version 6// showOnIdPage : show banner to SG visitors on ID pages â redirect to EN-SG version 7// keep false while EN-SG is still no-index / no public entry point 8const GEO_BANNER_ENABLED = { 9 showOnEnSgPage: true, 10 showOnIdPage: true, 11}; 12 13const GEO_BANNER_COPY = { 14 idVisitorOnSgPage: { 15 message: "Halo, apakah Anda ingin beralih ke situs berbahasa Indonesia kami?", 16 cta: "Beralih ke Bahasa Indonesia", 17 }, 18 sgVisitorOnIdPage: { 19 message: "Hello, do you want to switch to our Singaporean site?", 20 cta: "Switch to Singapore", 21 }, 22}; 23 24document.addEventListener("DOMContentLoaded", function () { 25 if (localStorage.getItem("hideGeoBanner") === "true") return; 26 27 // Read WPML hreflang tags and detect ID/EN-SG URLs by href pattern 28 const translations = getWPMLTranslations(); 29 30 // Only show banner if the current page exists in both ID and EN-SG 31 if (!translations.idUrl || !translations.enSgUrl) return; 32 33 const isEnSgPage = window.location.pathname.includes("/en-sg/"); 34 35 getCountryCode() 36 .then((country) => { 37 if (!country) return; 38 39 // ID visitor on EN-SG page 40 if (GEO_BANNER_ENABLED.showOnEnSgPage && country === "ID" && isEnSgPage) { 41 renderBanner( 42 GEO_BANNER_COPY.idVisitorOnSgPage.message, 43 translations.idUrl, 44 GEO_BANNER_COPY.idVisitorOnSgPage.cta, 45 { ctaId: "geo-banner-cta-to-id", closeId: "geo-banner-close-en-sg" }, 46 ); 47 } 48 // SG visitor on ID page 49 else if (GEO_BANNER_ENABLED.showOnIdPage && country === "SG" && !isEnSgPage) { 50 renderBanner( 51 GEO_BANNER_COPY.sgVisitorOnIdPage.message, 52 translations.enSgUrl, 53 GEO_BANNER_COPY.sgVisitorOnIdPage.cta, 54 { ctaId: "geo-banner-cta-to-en-sg", closeId: "geo-banner-close-id" }, 55 ); 56 } 57 }) 58 .catch(() => { 59 console.log("[geo-banner] Could not determine country code, all geo APIs unavailable."); 60 }); 61 62 function getWPMLTranslations() { 63 const get = (lang) => { 64 const el = document.querySelector(`link[rel="alternate"][hreflang="${lang}"]`); 65 return el ? el.getAttribute("href") : null; 66 }; 67 return { 68 idUrl: get("id"), 69 enSgUrl: get("en-sg"), 70 }; 71 } 72 73 function getCountryCode() { 74 if (GEO_BANNER_DEBUG) { 75 const param = new URLSearchParams(window.location.search).get("geo_debug"); 76 if (param) return Promise.resolve(param.toUpperCase()); 77 } 78 79 const cached = sessionStorage.getItem("geoCountryCode"); 80 if (cached) return Promise.resolve(cached); 81 82 return fetchCountryFrom("https://api.country.is/").then((code) => { 83 sessionStorage.setItem("geoCountryCode", code); 84 return code; 85 }); 86 } 87 88 function fetchCountryFrom(url) { 89 return fetch(url, { 90 headers: { 91 Accept: "application/json", 92 }, 93 }) 94 .then((response) => { 95 if (!response.ok) { 96 throw new Error("Geo request failed"); 97 } 98 return response.json(); 99 }) 100 .then((data) => { 101 const countryCode = (data.country_code || data.countryCode || data.country || "").toUpperCase(); 102 if (!countryCode) { 103 throw new Error("Country code missing"); 104 } 105 return countryCode; 106 }); 107 } 108 109 function renderBanner(msg, url, ctaLabel, ids) { 110 const banner = document.createElement("div"); 111 banner.id = "geo-alert-banner"; 112 banner.innerHTML = ` 113 <div class="geo-banner" style="position:fixed; bottom:0; left:0; width:100%; display: flex; justify-content: center; align-items: center; gap: 8px; background:#A51C1C; color:#fff; padding:16px; text-align:center; z-index:10000; font-family: 'Inter', sans-serif; font-size:16px; line-height:24px; font-weight:600; "> 114 <div class="icon"><span class="ic"></span><span class="pulse-1"></span><span class="pulse-2"></span></div> 115 <div class="content"> 116 ${msg} 117 <a id="${ids.ctaId}" href="${url}" style="color:#fff; display: inline-flex; font-weight:400; text-decoration:underline;">${ctaLabel}</a> 118 </div> 119 <button id="${ids.closeId}" onclick="closeGeoBanner()" style="background:none; border:none; cursor:pointer; display:flex; width:28px;height:28px; align-items:center; justify-content:center; position:absolute; right:20px; top:calc(50%-14px);"><svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M0 8.33333C0 3.73096 3.73096 0 8.33333 0C12.9357 0 16.6667 3.73096 16.6667 8.33333C16.6667 12.9357 12.9357 16.6667 8.33333 16.6667C3.73096 16.6667 0 12.9357 0 8.33333ZM5.57963 4.73783C5.34717 4.50538 4.97029 4.50538 4.73783 4.73783C4.50538 4.97029 4.50538 5.34717 4.73783 5.57963L7.49154 8.33333L4.73783 11.087C4.50538 11.3195 4.50538 11.6964 4.73783 11.9288C4.97029 12.1613 5.34717 12.1613 5.57963 11.9288L8.33333 9.17513L11.087 11.9288C11.3195 12.1613 11.6964 12.1613 11.9288 11.9288C12.1613 11.6964 12.1613 11.3195 11.9288 11.087L9.17513 8.33333L11.9288 5.57963C12.1613 5.34717 12.1613 4.97029 11.9288 4.73783C11.6964 4.50538 11.3195 4.50538 11.087 4.73783L8.33333 7.49154L5.57963 4.73783Z" fill="#F1F5F9"/></svg></button> 120 </div> 121 `; 122 document.body.prepend(banner); 123 document.body.classList.add("has-geobanner"); 124 } 125}); 126 127function closeGeoBanner() { 128 const banner = document.getElementById("geo-alert-banner"); 129 if (banner) { 130 banner.remove(); 131 } 132 document.body.classList.remove("has-geobanner"); 133 localStorage.setItem("hideGeoBanner", "true"); 134}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.