PageSourceSearch

https://marketersmedia.com/wp-content/themes/mm/assets/js/header.js

js marketersmedia.com collected 2026-10-02 03:04:09 UTC 9,235 bytes, 312 lines download raw bytes

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.