PageSourceSearch

https://www.esports.id/themes/2024/js/script.js?updated=1790969397

js esports.id collected 2026-10-02 19:30:14 UTC 23,270 bytes, 730 lines download raw bytes

1/*--- Write Javascript Here ---*/
2
3// Safe DOM manipulation helper functions
4const SafeDOM = {
5  // Safely get element by ID
6  getById: function(id) {
7    const element = document.getElementById(id);
8    return element || null;
9  },
10  
11  // Safely get elements by class name
12  getByClass: function(className) {
13    const elements = document.getElementsByClassName(className);
14    return elements && elements.length > 0 ? elements : null;
15  },
16  
17  // Safely get first element by class name
18  getFirstByClass: function(className) {
19    const elements = this.getByClass(className);
20    return elements ? elements[0] : null;
21  },
22  
23  // Safely set className for element by ID
24  setClassNameById: function(id, className) {
25    const element = this.getById(id);
26    if (element) {
27      element.className = className;
28      return true;
29    }
30    return false;
31  },
32  
33  // Safely set className for element by class
34  setClassNameByClass: function(className, newClassName) {
35    const element = this.getFirstByClass(className);
36    if (element) {
37      element.className = newClassName;
38      return true;
39    }
40    return false;
41  },
42  
43  // Safely add event listener
44  addEventListener: function(element, event, handler) {
45    if (element && typeof handler === 'function') {
46      element.addEventListener(event, handler);
47      return true;
48    }
49    return false;
50  }
51};
52
53$(document).ready(function(){
54    
55});
56
57new WOW().init();
58
59// Hamburger Mobile Menu
60var $hamburger = $(".hamburger");
61if ($hamburger.length > 0) {
62  $hamburger.on("click", function(e) {
63    $hamburger.toggleClass("is-active");
64    $('#nav-header-mobile').toggleClass('active');
65    $('.overlay-menu-mobile').toggleClass('active');
66  });
67}
68
69var $overlayMenu = $('.overlay-menu-mobile');
70if ($overlayMenu.length > 0) {
71  $overlayMenu.on('click', function(e) {
72    $hamburger.toggleClass("is-active");
73    $('#nav-header-mobile').toggleClass('active');
74    $('.overlay-menu-mobile').toggleClass('active');
75  });
76}
77
78// Header on scroll
79window.onscroll = function() {headerScroll()};
80
81function headerScroll() {
82  const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
83  const isScrolled = scrollTop > 50;
84  
85  if (isScrolled) {
86    SafeDOM.setClassNameById("header-top", "navbar navbar-expand-lg navbar-light bg-transparent d-none d-lg-flex active");
87    SafeDOM.setClassNameById("header-top-mobile", "navbar navbar-expand-lg navbar-light d-flex d-lg-none w-100 active");
88    SafeDOM.setClassNameById("header-menu-section", "navbar navbar-expand-lg navbar-light d-none d-lg-flex mb-3 w-100 active");
89    SafeDOM.setClassNameById("header-menu", "bg-theme-primary rounded-pill w-100 py-2 active");
90    SafeDOM.setClassNameById("logo-header-menu", "navbar-brand d-inline-flex me-0 bg-theme-dark px-3 py-2 rounded-pill active");
91    // SafeDOM.setClassNameByClass("audio-controls", "d-flex justify-content-between align-items-center bg-theme-primary rounded-pill px-3 py-2 w-100 h-100 audio-controls scrolled");
92  } else {
93    SafeDOM.setClassNameById("header-top", "navbar navbar-expand-lg navbar-light bg-transparent d-none d-lg-flex");
94    SafeDOM.setClassNameById("header-top-mobile", "navbar navbar-expand-lg navbar-light d-flex d-lg-none w-100 active");
95    SafeDOM.setClassNameById("header-menu-section", "navbar navbar-expand-lg navbar-light d-none d-lg-flex mb-3 w-100");
96    SafeDOM.setClassNameById("header-menu", "bg-theme-primary rounded-pill w-100 py-2");
97    SafeDOM.setClassNameById("logo-header-menu", "navbar-brand d-none me-0");
98    // SafeDOM.setClassNameByClass("audio-controls", "d-flex justify-content-between align-items-center bg-theme-primary rounded-pill px-3 py-2 w-100 h-100 audio-controls");
99  }
100}
101
102// Article Reading Functionality
103document.addEventListener("DOMContentLoaded", function () {
104  // Font size control elements
105  const decreaseBtn = document.getElementById("decreaseFont");
106  const increaseBtn = document.getElementById("increaseFont");
107  const resetBtn = document.getElementById("resetFont");
108  const currentSizeSpan = document.getElementById("currentFontSize");
109  const contentDiv = document.querySelector(".news-content");
110
111  // Font family control elements
112  const fontFamilySelect = document.getElementById("fontFamily");
113  const resetFontFamilyBtn = document.getElementById("resetFontFamily");
114
115  // Text-to-speech control elements
116  const playAudioBtn = document.getElementById("playAudio");
117  const pauseAudioBtn = document.getElementById("pauseAudio");
118  const stopAudioBtn = document.getElementById("stopAudio");
119
120  // If elements don't exist (not on news page), exit early
121  if (!contentDiv) {
122    return;
123  }
124
125  // Font size configuration
126  const fontSizes = {
127    "fs-11": "0.688rem",
128    "fs-12": "0.75rem",
129    "fs-13": "0.813rem",
130    "fs-14": "0.875rem",
131    "fs-15": "0.938rem",
132    "fs-16": "1rem",
133    "fs-17": "1.063rem",
134    "fs-18": "1.125rem",
135    "fs-19": "1.188rem",
136    "fs-20": "1.25rem",
137    "fs-21": "1.313rem",
138    "fs-22": "1.375rem",
139    "fs-23": "1.438rem",
140    "fs-24": "1.5rem",
141    "fs-25": "1.563rem",
142  };
143
144  // Font family configuration
145  const fontFamilies = {
146    inter: "Inter, sans-serif",
147    roboto: "Roboto, sans-serif",
148    poppins: "Poppins, sans-serif",
149    newsreader: "Newsreader, serif",
150    arial: "Arial, sans-serif",
151    georgia: "Georgia, serif",
152    times: "Times New Roman, serif",
153  };
154
155  // Text-to-speech configuration
156  let speechSynthesis = window.speechSynthesis;
157  let speechUtterance = null;
158  let isPlaying = false;
159  let isPaused = false;
160  let currentText = "";
161  let currentPosition = 0;
162  let pauseStartTime = 0;
163  let totalPauseTime = 0;
164  let startTime = 0;
165  let voicesLoaded = false;
166  let availableVoices = [];
167
168  // Chrome-compatible voice loading function
169  function loadVoices() {
170    return new Promise((resolve) => {
171      // Chrome fix: Sometimes voices aren't loaded immediately
172      const voices = speechSynthesis.getVoices();
173
174      if (voices.length > 0) {
175        availableVoices = voices;
176        voicesLoaded = true;
177        console.log("Voices loaded immediately:", voices.length);
178        resolve(voices);
179        return;
180      }
181
182      // Wait for voices to load
183      const onVoicesChanged = () => {
184        const loadedVoices = speechSynthesis.getVoices();
185        if (loadedVoices.length > 0) {
186          availableVoices = loadedVoices;
187          voicesLoaded = true;
188          console.log("Voices loaded via event:", loadedVoices.length);
189
190          // Remove event listener to prevent multiple calls
191          speechSynthesis.removeEventListener("voiceschanged", onVoicesChanged);
192          resolve(loadedVoices);
193        }
194      };
195
196      // Add event listener
197      speechSynthesis.addEventListener("voiceschanged", onVoicesChanged);
198
199      // Chrome fix: Sometimes we need to trigger voice loading manually
200      setTimeout(() => {
201        const retryVoices = speechSynthesis.getVoices();
202        if (retryVoices.length > 0) {
203          availableVoices = retryVoices;
204          voicesLoaded = true;
205          console.log("Voices loaded via timeout:", retryVoices.length);
206          speechSynthesis.removeEventListener("voiceschanged", onVoicesChanged);
207          resolve(retryVoices);
208        }
209      }, 1000);
210
211      // Trigger voices loading
212      speechSynthesis.getVoices();
213    });
214  }
215
216  // Check if speech synthesis is supported
217  if (!speechSynthesis) {
218    // Hide audio controls if not supported
219    [playAudioBtn, pauseAudioBtn, stopAudioBtn].forEach((btn) => {
220      if (btn) btn.style.display = "none";
221    });
222  } else {
223    // Load voices when page loads
224    loadVoices()
225      .then((voices) => {
226        const indonesianVoices = voices.filter(
227          (voice) =>
228            voice.lang === "id-ID" ||
229            voice.lang === "id" ||
230            voice.name.toLowerCase().includes("indonesia") ||
231            voice.name.toLowerCase().includes("indonesian")
232        );
233
234        if (indonesianVoices.length > 0) {
235          console.log(
236            "Available Indonesian voices:",
237            indonesianVoices.map((v) => `${v.name} (${v.lang})`)
238          );
239        } else {
240          console.log("No Indonesian voices found, will use fallback");
241        }
242      })
243      .catch((error) => {
244        console.error("Error loading voices:", error);
245      });
246  }
247
248  let currentFontIndex = 5; // Start with fs-14 (index 5)
249  const fontKeys = Object.keys(fontSizes);
250
251  // Initialize font size
252  function updateFontSize() {
253    const currentFontClass = fontKeys[currentFontIndex];
254    const currentSize = fontSizes[currentFontClass];
255
256    // Remove all font size classes
257    fontKeys.forEach((key) => {
258      contentDiv.classList.remove(key);
259    });
260
261    // Add current font size class
262    contentDiv.classList.add(currentFontClass);
263
264    // Update display - convert rem to px for better readability
265    const fontSizeInPx = Math.round(parseFloat(currentSize) * 16); // 1rem = 16px
266    if (currentSizeSpan) {
267      currentSizeSpan.textContent = fontSizeInPx;
268    }
269
270    // Store in localStorage
271    localStorage.setItem("articleFontSize", currentFontIndex);
272
273    // Update button states
274    if (decreaseBtn) {
275      decreaseBtn.disabled = currentFontIndex === 0;
276      if (decreaseBtn.disabled) {
277        decreaseBtn.style.opacity = "0.5";
278        decreaseBtn.style.cursor = "not-allowed";
279      } else {
280        decreaseBtn.style.opacity = "1";
281        decreaseBtn.style.cursor = "pointer";
282      }
283    }
284
285    if (increaseBtn) {
286      increaseBtn.disabled = currentFontIndex === fontKeys.length - 1;
287      if (increaseBtn.disabled) {
288        increaseBtn.style.opacity = "0.5";
289        increaseBtn.style.cursor = "not-allowed";
290      } else {
291        increaseBtn.style.opacity = "1";
292        increaseBtn.style.cursor = "pointer";
293      }
294    }
295  }
296
297  // Initialize font family
298  function updateFontFamily() {
299    if (!fontFamilySelect) return;
300
301    const selectedFont = fontFamilySelect.value;
302    const fontFamilyValue = fontFamilies[selectedFont];
303
304    // Apply font family to content
305    contentDiv.style.fontFamily = fontFamilyValue;
306
307    // Store in localStorage
308    localStorage.setItem("articleFontFamily", selectedFont);
309  }
310
311  // Text-to-speech functions
312  function playArticle() {
313    if (speechSynthesis && !isPlaying) {
314      try {
315        // Chrome fix: Ensure speech synthesis is not paused
316        if (speechSynthesis.paused) {
317          speechSynthesis.resume();
318        }
319
320        // Chrome fix: Cancel any existing speech to prevent conflicts
321        if (speechSynthesis.speaking) {
322          speechSynthesis.cancel();
323        }
324
325        // If resuming from pause, calculate the exact position
326        if (isPaused && currentText) {
327          // Calculate how much time has passed since we started
328          const currentTime = Date.now();
329          const totalElapsedTime =
330            (currentTime - startTime - totalPauseTime) / 1000;
331
332          // Estimate position based on time (assuming ~150 words per minute)
333          const wordsPerMinute = 150;
334          const wordsElapsed = Math.floor(
335            (totalElapsedTime * wordsPerMinute) / 60
336          );
337          const words = currentText.split(" ");
338
339          if (wordsElapsed < words.length) {
340            // Resume from calculated position
341            const remainingWords = words.slice(wordsElapsed);
342            const remainingText = remainingWords.join(" ");
343            speechUtterance = new SpeechSynthesisUtterance(remainingText);
344
345            // Update position tracking
346            currentPosition = wordsElapsed;
347            isPaused = false;
348
349            console.log(
350              `Resuming from word ${wordsElapsed} of ${
351                words.length
352              }: "${remainingWords.slice(0, 3).join(" ")}..."`
353            );
354          } else {
355            // If we've reached the end, start over
356            currentText = contentDiv.innerText || contentDiv.textContent;
357            currentPosition = 0;
358            speechUtterance = new SpeechSynthesisUtterance(currentText);
359            isPaused = false;
360          }
361        } else {
362          // Start fresh - get the article content
363          currentText = contentDiv.innerText || contentDiv.textContent;
364          currentPosition = 0;
365          speechUtterance = new SpeechSynthesisUtterance(currentText);
366          isPaused = false;
367          totalPauseTime = 0;
368        }
369
370        // Configure speech settings
371        speechUtterance.lang = "id-ID"; // Indonesian language
372        speechUtterance.rate = 0.85; // Slightly slower for better Indonesian pronunciation
373        speechUtterance.pitch = 1.0; // Normal pitch
374        speechUtterance.volume = 1.0; // Full volume
375
376        // Use the pre-loaded voices
377        if (!voicesLoaded || availableVoices.length === 0) {
378          console.log("Voices not loaded yet, loading now...");
379          loadVoices()
380            .then((voices) => {
381              availableVoices = voices;
382              selectVoiceAndSpeak();
383            })
384            .catch((error) => {
385              console.error("Failed to load voices:", error);
386              console.log(
387                "Gagal memuat voice. Silakan refresh halaman dan coba lagi."
388              );
389            });
390          return;
391        }
392
393        selectVoiceAndSpeak();
394
395        function selectVoiceAndSpeak() {
396          const indonesianVoice = availableVoices.find(
397            (voice) =>
398              voice.lang === "id-ID" ||
399              voice.lang === "id" ||
400              voice.name.toLowerCase().includes("indonesia") ||
401              voice.name.toLowerCase().includes("indonesian")
402          );
403
404          if (indonesianVoice) {
405            speechUtterance.voice = indonesianVoice;
406            console.log("Using Indonesian voice:", indonesianVoice.name);
407          } else {
408            // Fallback to any available voice
409            const fallbackVoices = availableVoices.filter(
410              (voice) =>
411                voice.lang.startsWith("id") || voice.lang.startsWith("en")
412            );
413            if (fallbackVoices.length > 0) {
414              speechUtterance.voice = fallbackVoices[0];
415              console.log(
416                "Using fallback voice:",
417                fallbackVoices[0].name,
418                "for language:",
419                fallbackVoices[0].lang
420              );
421            } else {
422              // Use first available voice
423              if (availableVoices.length > 0) {
424                speechUtterance.voice = availableVoices[0];
425                console.log(
426                  "Using first available voice:",
427                  availableVoices[0].name,
428                  "for language:",
429                  availableVoices[0].lang
430                );
431              } else {
432                console.log("No voices available");
433              }
434            }
435          }
436
437          // Event handlers
438          speechUtterance.onstart = function () {
439            isPlaying = true;
440            if (isPaused) {
441              // If resuming, add the pause time to total
442              totalPauseTime += Date.now() - pauseStartTime;
443            } else {
444              // If starting fresh, reset pause time
445              startTime = Date.now();
446              totalPauseTime = 0;
447            }
448            updateAudioButtonStates();
449
450            // Start progress updates
451            startProgressUpdates();
452          };
453
454          speechUtterance.onend = function () {
455            isPlaying = false;
456            isPaused = false;
457            currentPosition = 0;
458            totalPauseTime = 0;
459            updateAudioButtonStates();
460          };
461
462          speechUtterance.onerror = function (event) {
463            isPlaying = false;
464            isPaused = false;
465            currentPosition = 0;
466            totalPauseTime = 0;
467            updateAudioButtonStates();
468            console.error("Speech synthesis error:", event.error);
469
470            // Chrome-specific error handling
471            if (event.error === "not-allowed") {
472              console.log(
473                "Audio tidak diizinkan. Pastikan halaman sudah diinteraksi."
474              );
475            } else {
476              console.log("Error audio: " + event.error);
477            }
478          };
479
480          // Start speaking
481          try {
482            speechSynthesis.speak(speechUtterance);
483            console.log("Speech synthesis started");
484          } catch (error) {
485            console.error("Error starting speech synthesis:", error);
486            console.log("Gagal memulai audio. Silakan coba lagi.");
487          }
488        }
489      } catch (error) {
490        console.error("Error in playArticle:", error);
491        console.log("Error: " + error.message);
492      }
493    }
494  }
495
496  function pauseArticle() {
497    if (speechSynthesis && isPlaying) {
498      // Calculate current position before pausing
499      const currentTime = Date.now();
500      const elapsedTime = (currentTime - startTime - totalPauseTime) / 1000;
501
502      // Estimate position based on time (assuming ~150 words per minute)
503      const wordsPerMinute = 150;
504      const wordsElapsed = Math.floor((elapsedTime * wordsPerMinute) / 60);
505      const words = currentText.split(" ");
506
507      currentPosition = Math.min(wordsElapsed, words.length);
508
509      // Store pause start time
510      pauseStartTime = currentTime;
511
512      // Cancel current speech
513      speechSynthesis.cancel();
514      isPlaying = false;
515      isPaused = true;
516
517      // Stop progress updates
518      stopProgressUpdates();
519      updateAudioButtonStates();
520
521      console.log(
522        `Paused at word ${currentPosition} of ${words.length}: "${words
523          .slice(Math.max(0, currentPosition - 2), currentPosition + 1)
524          .join(" ")}..."`
525      );
526    }
527  }
528
529  function stopArticle() {
530    if (speechSynthesis) {
531      speechSynthesis.cancel();
532      isPlaying = false;
533      isPaused = false;
534      currentPosition = 0;
535      currentText = "";
536      totalPauseTime = 0;
537
538      // Stop progress updates
539      stopProgressUpdates();
540      updateAudioButtonStates();
541    }
542  }
543
544  function updateAudioButtonStates() {
545    if (!playAudioBtn || !pauseAudioBtn || !stopAudioBtn) return;
546
547    playAudioBtn.disabled = isPlaying;
548    pauseAudioBtn.disabled = !isPlaying;
549    stopAudioBtn.disabled = !isPlaying && !isPaused;
550
551    // Update button styles and text
552    if (isPlaying) {
553      playAudioBtn.style.opacity = "0.5";
554      playAudioBtn.style.cursor = "not-allowed";
555      playAudioBtn.innerHTML = '<i class="fas fa-play"></i>';
556    } else if (isPaused) {
557      playAudioBtn.style.opacity = "1";
558      playAudioBtn.style.cursor = "pointer";
559      playAudioBtn.innerHTML = '<i class="fas fa-play"></i>';
560    } else {
561      playAudioBtn.style.opacity = "1";
562      playAudioBtn.style.cursor = "pointer";
563      playAudioBtn.innerHTML = '<i class="fas fa-play"></i>';
564    }
565
566    // Update progress display
567    updateAudioProgress();
568  }
569
570  function updateAudioProgress() {
571    const progressSpan = document.getElementById("audioProgress");
572    if (progressSpan) {
573      if (isPlaying) {
574        const currentTime = Date.now();
575        const elapsedTime = (currentTime - startTime - totalPauseTime) / 1000;
576        const wordsPerMinute = 150;
577        const wordsElapsed = Math.floor((elapsedTime * wordsPerMinute) / 60);
578        const words = currentText.split(" ");
579        const progress = Math.min(
580          Math.round((wordsElapsed / words.length) * 100),
581          100
582        );
583        progressSpan.textContent = `${progress}% (${wordsElapsed}/${words.length} words)`;
584      } else if (isPaused) {
585        const words = currentText.split(" ");
586        const progress = Math.min(
587          Math.round((currentPosition / words.length) * 100),
588          100
589        );
590        progressSpan.textContent = `Paused: ${progress}% (${currentPosition}/${words.length} words)`;
591      } else {
592        progressSpan.textContent = "";
593      }
594    }
595  }
596
597  let progressInterval = null;
598
599  function startProgressUpdates() {
600    // Clear any existing interval
601    if (progressInterval) {
602      clearInterval(progressInterval);
603    }
604
605    // Update progress every 500ms while playing
606    progressInterval = setInterval(() => {
607      if (isPlaying) {
608        updateAudioProgress();
609      } else {
610        clearInterval(progressInterval);
611        progressInterval = null;
612      }
613    }, 500);
614  }
615
616  function stopProgressUpdates() {
617    if (progressInterval) {
618      clearInterval(progressInterval);
619      progressInterval = null;
620    }
621  }
622
623  // Event listeners
624  if (decreaseBtn) {
625    decreaseBtn.addEventListener("click", function () {
626      if (currentFontIndex > 0) {
627        currentFontIndex--;
628        updateFontSize();
629      }
630    });
631  }
632
633  if (increaseBtn) {
634    increaseBtn.addEventListener("click", function () {
635      if (currentFontIndex < fontKeys.length - 1) {
636        currentFontIndex++;
637        updateFontSize();
638      }
639    });
640  }
641
642  if (resetBtn) {
643    resetBtn.addEventListener("click", function () {
644      currentFontIndex = 5; // Reset to fs-14
645      updateFontSize();
646    });
647  }
648
649  // Font family event listeners
650  if (fontFamilySelect) {
651    fontFamilySelect.addEventListener("change", updateFontFamily);
652  }
653
654  if (resetFontFamilyBtn) {
655    resetFontFamilyBtn.addEventListener("click", function () {
656      fontFamilySelect.value = "inter"; // Reset to default Inter font
657      updateFontFamily();
658    });
659  }
660
661  // Text-to-speech event listeners
662  if (playAudioBtn) {
663    playAudioBtn.addEventListener("click", function () {
664      playArticle();
665    });
666  }
667
668  if (pauseAudioBtn) {
669    pauseAudioBtn.addEventListener("click", pauseArticle);
670  }
671
672  if (stopAudioBtn) {
673    stopAudioBtn.addEventListener("click", stopArticle);
674  }
675
676  // Keyboard support
677  document.addEventListener("keydown", function (e) {
678    if (e.ctrlKey || e.metaKey) {
679      if (e.key === "=" || e.key === "+") {
680        e.preventDefault();
681        if (currentFontIndex < fontKeys.length - 1) {
682          currentFontIndex++;
683          updateFontSize();
684        }
685      } else if (e.key === "-") {
686        e.preventDefault();
687        if (currentFontIndex > 0) {
688          currentFontIndex--;
689          updateFontSize();
690        }
691      } else if (e.key === "0") {
692        e.preventDefault();
693        currentFontIndex = 5; // Reset to fs-14
694        updateFontSize();
695      } else if (e.key === "f") {
696        e.preventDefault();
697        if (fontFamilySelect) fontFamilySelect.focus();
698      } else if (e.key === "p") {
699        e.preventDefault();
700        if (isPlaying) {
701          pauseArticle();
702        } else if (isPaused) {
703          playArticle(); // Resume from pause
704        } else {
705          playArticle(); // Start fresh
706        }
707      } else if (e.key === "s") {
708        e.preventDefault();
709        stopArticle();
710      }
711    }
712  });
713
714  // Load saved font size from localStorage
715  const savedFontSize = localStorage.getItem("articleFontSize");
716  if (savedFontSize !== null) {
717    currentFontIndex = parseInt(savedFontSize);
718  }
719
720  // Load saved font family from localStorage
721  const savedFontFamily = localStorage.getItem("articleFontFamily");
722  if (savedFontFamily !== null && fontFamilySelect) {
723    fontFamilySelect.value = savedFontFamily;
724  }
725
726  // Initialize
727  updateFontSize();
728  updateFontFamily();
729  updateAudioButtonStates();
730});

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.