1// Open and close the menu on click 2const hamburgerDropdown = document.getElementById("hamburger-dropdown"); 3 4// Toggle menu visibility 5function toggleHamburgerMenu(event) { 6 // To make sure the menu does not change its display when its list item is clicked 7 if (event.target.className !== "hamburger-dropdown-trigger") { 8 if (hamburgerDropdown.style.display === "flex") { 9 hamburgerDropdown.style.display = "none"; 10 } else { 11 hamburgerDropdown.style.display = "flex"; 12 } 13 } 14} 15 16// Hamburger dropdown -> opening and closing 17function toggleHamburgerDropdown(event) { 18 const id = event.target.id; 19 const splitId = id.split("-"); 20 const dropdown = splitId[1]; 21 const triggerDivImg = document.querySelector(`#${id} img`); 22 const dropdownContent = document.getElementById( 23 `hamburger-${dropdown}-dropdown`, 24 ); 25 26 if (event.target.classList.contains("hamburger-dropdown-trigger")) { 27 if (dropdownContent.style.display === "flex") { 28 dropdownContent.style.display = "none"; 29 triggerDivImg.style.opacity = "0.38"; 30 } else { 31 dropdownContent.style.display = "flex"; 32 triggerDivImg.style.opacity = "1"; 33 } 34 } 35} 36 37const overlay = document.getElementById("dropdown-overlay"); 38 39document.querySelectorAll(".nav-dropdown-link").forEach((nav) => { 40 // Add event listener for mouse enter 41 nav.addEventListener("mouseenter", () => { 42 overlay.style.display = "block"; 43 }); 44 45 // Add event listener for mouse leave 46 nav.addEventListener("mouseleave", () => { 47 overlay.style.display = "none"; 48 }); 49}); 50 51document.addEventListener("DOMContentLoaded", function () { 52 // Grab the main div by id 53 let mainDiv = document.querySelector("body > div"); 54 55 if (mainDiv) { 56 mainDiv.addEventListener("click", (event) => { 57 // Only call function - which toggle the visibility - when the menu is visible 58 // Otherwise menu will also open when the body is clicked 59 if (hamburgerDropdown.style.display === "flex") { 60 toggleHamburgerMenu(event); 61 } 62 }); 63 } 64 65 // For handling the pages where there's no main div (like in the new product pages) 66 let sections = document.querySelectorAll("section"); 67 68 // Loop the sections and add event listener to listen to click 69 // Then close the hamburger menu if it's opened 70 for (let i = 0; i < sections.length; i++) { 71 sections[i].addEventListener("click", (event) => { 72 if (hamburgerDropdown.style.display === "flex") { 73 toggleHamburgerMenu(event); 74 } 75 }); 76 } 77 78 // Initialize Nikkei teaser 79 initNikkeiTeaser(); 80}); 81 82// Exit intent handling 83let exitIntentTriggered = false; 84let nikkeiExitShown = false; 85 86document.addEventListener("mouseleave", (e) => { 87 // Only trigger when mouse leaves from the top of the page 88 if (e.clientY <= 0 && !exitIntentTriggered) { 89 // showUTExitPopup(); 90 } 91 if (e.clientY <= 0 && !nikkeiExitShown) { 92 nikkeiExitShown = true; 93 showNikkeiExitPopup(); 94 } 95}); 96 97function showUTExitPopup() { 98 const popup = document.getElementById("ut-exit-popup"); 99 if (popup && !exitIntentTriggered) { 100 popup.classList.add("active"); 101 exitIntentTriggered = true; 102 103 // Close popup when clicking outside 104 popup.addEventListener("click", function (e) { 105 if (e.target === popup) { 106 closeUTExitPopup(); 107 } 108 }); 109 } 110} 111 112function closeUTExitPopup() { 113 const popup = document.getElementById("ut-exit-popup"); 114 if (popup) { 115 popup.classList.remove("active"); 116 } 117} 118 119// ============================================================= 120// Nikkei Teaser â hello bar + exit popup + email confirmation 121// Set to true to re-enable 122// ============================================================= 123const nikkeiTeaserEnabled = true; 124 125function initNikkeiTeaser() { 126 if (!nikkeiTeaserEnabled) return; 127 const submitted = localStorage.getItem("nikkeiEmailSubmitted") === "true"; 128 const bar = document.getElementById("nk-bar"); 129 if (bar && submitted) { 130 bar.style.display = "none"; 131 } 132 nikkeiStartCountdown(); 133} 134 135function nikkeiStartCountdown() { 136 // D-Day: 1 June 2026, 10:00 PM MYT (UTC+8) 137 const target = new Date("2026-06-03T22:00:00+08:00").getTime(); 138
139 function update() { 140 const diff = Math.max(0, target - Date.now()); 141 const totalMin = Math.floor(diff / 60000); 142 const dd = String(Math.floor(totalMin / (60 * 24))).padStart(2, "0"); 143 const hh = String(Math.floor((totalMin % (60 * 24)) / 60)).padStart(2, "0"); 144 const mm = String(totalMin % 60).padStart(2, "0"); 145 146 const ddEl = document.getElementById("nk-cd-dd"); 147 const hhEl = document.getElementById("nk-cd-hh"); 148 const mmEl = document.getElementById("nk-cd-mm"); 149 if (ddEl) ddEl.textContent = dd; 150 if (hhEl) hhEl.textContent = hh; 151 if (mmEl) mmEl.textContent = mm; 152 } 153 154 update(); 155 setInterval(update, 1000); 156} 157 158function showNikkeiExitPopup() { 159 if (!nikkeiTeaserEnabled) return; 160 if (localStorage.getItem("nikkeiEmailSubmitted") === "true") return; 161 const overlay = document.getElementById("nk-exit"); 162 if (overlay) { 163 overlay.classList.add("active"); 164 overlay.addEventListener( 165 "click", 166 (e) => { 167 if (e.target === overlay) closeNikkeiExitPopup(); 168 }, 169 { once: true }, 170 ); 171 } 172} 173 174function closeNikkeiExitPopup() { 175 const overlay = document.getElementById("nk-exit"); 176 if (overlay) overlay.classList.remove("active"); 177} 178 179function closeNikkeiBar() { 180 const bar = document.getElementById("nk-bar"); 181 if (bar) bar.style.display = "none"; 182} 183 184// ============================================================= 185// reCAPTCHA â explicit render into hidden containers so we can 186// validate the email before ever executing the challenge. 187// ============================================================= 188let _nkWidgetBar = null; 189let _nkWidgetExit = null; 190 191// Called by the reCAPTCHA API itself via ?onload=nkInitRecaptcha, 192// guaranteeing grecaptcha is fully ready â no load-event race condition. 193function nkInitRecaptcha() { 194 const barEl = document.getElementById("nk-recaptcha-bar"); 195 const exitEl = document.getElementById("nk-recaptcha-exit"); 196 if (barEl && _nkWidgetBar === null) { 197 _nkWidgetBar = grecaptcha.render(barEl, { 198 sitekey: "6Le2cjIcAAAAAPsTQfWCMyj1m0AzBsyL24QdHug6", 199 size: "invisible", 200 callback: handleNikkeiBarRecaptcha, 201 }); 202 } 203 if (exitEl && _nkWidgetExit === null) { 204 _nkWidgetExit = grecaptcha.render(exitEl, { 205 sitekey: "6Le2cjIcAAAAAPsTQfWCMyj1m0AzBsyL24QdHug6", 206 size: "invisible", 207 callback: handleNikkeiExitRecaptcha, 208 }); 209 } 210} 211 212function _nkEmailValid(email) { 213 return email.length > 0 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); 214} 215 216function nikkeiSubmitBar() { 217 const form = document.getElementById("nk-bar-form"); 218 if (!form) return; 219 const email = form.email.value.trim(); 220 if (!_nkEmailValid(email)) { 221 form.email.focus(); 222 return; 223 } 224 if (_nkWidgetBar !== null) grecaptcha.execute(_nkWidgetBar); 225} 226 227function nikkeiSubmitExit() { 228 const form = document.getElementById("nk-exit-form"); 229 if (!form) return; 230 const email = form.email.value.trim(); 231 if (!_nkEmailValid(email)) { 232 form.email.focus(); 233 return; 234 } 235 if (_nkWidgetExit !== null) grecaptcha.execute(_nkWidgetExit); 236} 237 238function handleNikkeiBarRecaptcha(token) { 239 _nikkeiDoSubmit("nk-bar-form", token, _nkWidgetBar); 240} 241 242function handleNikkeiExitRecaptcha(token) { 243 _nikkeiDoSubmit("nk-exit-form", token, _nkWidgetExit); 244} 245 246async function _nikkeiDoSubmit(formId, token, widgetId) { 247 const form = document.getElementById(formId); 248 if (!form) return; 249 250 const email = form.email.value.trim(); 251 const btn = form.querySelector("button[type='button']"); 252 const origText = btn.innerHTML; 253 254 btn.disabled = true; 255 btn.innerHTML = "Submitting..."; 256 257 try { 258 const res = await fetch( 259 "https://apis.studio.marketersmedia.com/misc/lock-in-nikkei-preview", 260 { 261 method: "POST", 262 headers: { "Content-Type": "application/json" }, 263 body: JSON.stringify({ email, recaptchaToken: token }), 264 }, 265 ); 266 267 if (!res.ok) throw new Error("Failed"); 268 269 localStorage.setItem("nikkeiEmailSubmitted", "true"); 270 271 const bar = document.getElementById("nk-bar"); 272 if (bar) bar.style.display = "none"; 273 closeNikkeiExitPopup(); 274 showNikkeiConfirm(email); 275 276 form.reset(); 277 btn.disabled = false;
278 btn.innerHTML = origText; 279 grecaptcha.reset(widgetId); 280 } catch (err) { 281 console.error(err); 282 btn.innerHTML = "Error â Try Again"; 283 btn.style.background = "#b71c1c"; 284 setTimeout(() => { 285 btn.disabled = false; 286 btn.innerHTML = origText; 287 btn.style.background = "#f22f2f"; 288 grecaptcha.reset(widgetId); 289 }, 3000); 290 } 291} 292 293function showNikkeiConfirm(email) { 294 const overlay = document.getElementById("nk-confirm"); 295 const emailEl = document.getElementById("nk-confirm-email"); 296 if (overlay) { 297 if (emailEl) emailEl.textContent = email; 298 overlay.classList.add("active"); 299 overlay.addEventListener( 300 "click", 301 (e) => { 302 if (e.target === overlay) closeNikkeiConfirm(); 303 }, 304 { once: true }, 305 ); 306 } 307} 308 309function closeNikkeiConfirm() { 310 const overlay = document.getElementById("nk-confirm"); 311 if (overlay) overlay.classList.remove("active"); 312}
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.