From 4c372d3817f58c8a24c4986095c3c6656900beaa Mon Sep 17 00:00:00 2001 From: Shubham Oulkar Date: Sun, 6 Jul 2025 15:35:18 +0530 Subject: [PATCH 1/9] add scroll-to-top and scroll class toggle with vanilla JS --- css/style.css | 6 ++--- js/app.js | 62 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/css/style.css b/css/style.css index 86e124fe4b..f68a13cb00 100644 --- a/css/style.css +++ b/css/style.css @@ -481,14 +481,14 @@ pre:has(code) button.failed { } } -/* top button */ +/* scroll to top button */ -.scroll #top { +body.scroll #top { opacity: .5; display: initial } -.scroll #top:hover { +body.scroll #top:hover { opacity: 1; } diff --git a/js/app.js b/js/app.js index 2418c2f3a9..2ebeeb47ed 100644 --- a/js/app.js +++ b/js/app.js @@ -1,24 +1,51 @@ -$(function(){ - var doc = $(document); - - // top link - $('#top').click(function(e){ - $('html, body').animate({scrollTop : 0}, 500); - return false; +document.addEventListener("DOMContentLoaded", function () { + + // scroll to top of the page + document.getElementById("top")?.addEventListener("click", function (e) { + e.preventDefault(); + window.scrollTo({ + top: 0, + behavior: "smooth" + }) }); - // scrolling links - var added; - doc.scroll(function(e){ - if (doc.scrollTop() > 5) { + // add/remove class 'scroll' on verticle scroll by 5px + let added = false; + window.addEventListener('scroll', function () { + if (window.scrollY > 5) { if (added) return; added = true; - $('body').addClass('scroll'); + document.body.classList.add('scroll'); } else { - $('body').removeClass('scroll'); + document.body.classList.remove('scroll'); added = false; } - }) + }); +}); + + + +$(function(){ + var doc = $(document); + + // top link + // $('#top').click(function(e){ + // $('html, body').animate({scrollTop : 0}, 500); + // return false; + // }); + + // scrolling links + // var added; + // doc.scroll(function(e){ + // if (doc.scrollTop() > 5) { + // if (added) return; + // added = true; + // $('body').addClass('scroll'); + // } else { + // $('body').removeClass('scroll'); + // added = false; + // } + // }) // menu bar @@ -117,6 +144,11 @@ function readCookie(name) { return null; } +/* + +Investigate following function + function eraseCookie(name) { createCookie(name, "", -1); -} \ No newline at end of file +} +*/ \ No newline at end of file From 47bf6fc9e107e121cd218eaa4ad4d3954df91284 Mon Sep 17 00:00:00 2001 From: Shubham Oulkar Date: Sun, 6 Jul 2025 15:53:58 +0530 Subject: [PATCH 2/9] add language detection --- js/app.js | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/js/app.js b/js/app.js index 2ebeeb47ed..5d6179090b 100644 --- a/js/app.js +++ b/js/app.js @@ -1,4 +1,15 @@ document.addEventListener("DOMContentLoaded", function () { + // display current language in language picker component + const languageElement = document.getElementById('languageData'); + const languagesData = languageElement ? JSON.parse(languageElement.dataset.languages) : []; + + const langDisplay = document.getElementById('current-lang'); + + if (langDisplay) { + const currentLanguage = window.location.pathname.split('/')[1]; + const matchedLang = languagesData.find(lang => lang.code === currentLanguage); + langDisplay.textContent = matchedLang ? matchedLang.name : 'English'; + } // scroll to top of the page document.getElementById("top")?.addEventListener("click", function (e) { @@ -70,18 +81,18 @@ $(function(){ var parentMenuSelector; var lastApiPrefix; - if (document.readyState !== 'loading') { - const languageElement = document.getElementById('languageData'); - const languagesData = languageElement ? JSON.parse(languageElement.dataset.languages) : []; + // if (document.readyState !== 'loading') { + // const languageElement = document.getElementById('languageData'); + // const languagesData = languageElement ? JSON.parse(languageElement.dataset.languages) : []; - const langDisplay = document.getElementById('current-lang'); + // const langDisplay = document.getElementById('current-lang'); - if (langDisplay) { - const currentLanguage = window.location.pathname.split('/')[1]; - const matchedLang = languagesData.find(lang => lang.code === currentLanguage); - langDisplay.textContent = matchedLang ? matchedLang.name : 'English'; - } - } + // if (langDisplay) { + // const currentLanguage = window.location.pathname.split('/')[1]; + // const matchedLang = languagesData.find(lang => lang.code === currentLanguage); + // langDisplay.textContent = matchedLang ? matchedLang.name : 'English'; + // } + // } $(document).scroll(function() { var h = closest(); From 9386a9582f5b7288fbbadf6f2eb5eea213e8caff Mon Sep 17 00:00:00 2001 From: Shubham Oulkar Date: Mon, 21 Jul 2025 11:10:01 +0530 Subject: [PATCH 3/9] add i18n --- js/app.js | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/js/app.js b/js/app.js index 5d6179090b..2a8980fa35 100644 --- a/js/app.js +++ b/js/app.js @@ -1,10 +1,11 @@ document.addEventListener("DOMContentLoaded", function () { - // display current language in language picker component const languageElement = document.getElementById('languageData'); const languagesData = languageElement ? JSON.parse(languageElement.dataset.languages) : []; - const langDisplay = document.getElementById('current-lang'); + const i18nMsgBox = document.getElementById("i18n-notice-box"); + const scrollToTopBtn = document.getElementById("top"); + // display current language in language picker component if (langDisplay) { const currentLanguage = window.location.pathname.split('/')[1]; const matchedLang = languagesData.find(lang => lang.code === currentLanguage); @@ -12,7 +13,7 @@ document.addEventListener("DOMContentLoaded", function () { } // scroll to top of the page - document.getElementById("top")?.addEventListener("click", function (e) { + scrollToTopBtn.addEventListener("click", function (e) { e.preventDefault(); window.scrollTo({ top: 0, @@ -20,7 +21,7 @@ document.addEventListener("DOMContentLoaded", function () { }) }); - // add/remove class 'scroll' on verticle scroll by 5px + // add/remove class 'scroll' on scroll by 5px let added = false; window.addEventListener('scroll', function () { if (window.scrollY > 5) { @@ -32,6 +33,17 @@ document.addEventListener("DOMContentLoaded", function () { added = false; } }); + + // i18n message box + if(readCookie('i18nClose')){ + i18nMsgBox.hidden = true; + } else { + const closeI18nBtn = document.getElementById("close-i18n-notice-box"); + closeI18nBtn.addEventListener("click", ()=>{ + i18nMsgBox.hidden = true; + createCookie('i18nClose', 1); + }); + } }); @@ -116,17 +128,17 @@ $(function(){ }) // i18n notice - if (readCookie('i18nClose')) { - $('#i18n-notice-box').hide(); - $("#i18n-notice-box").addClass("hidden"); - } - else { - $('#close-i18n-notice-box').on('click', function () { - $('#i18n-notice-box').hide(); - $("#i18n-notice-box").addClass("hidden"); - createCookie('i18nClose', 1); - }) - } + // if (readCookie('i18nClose')) { + // $('#i18n-notice-box').hide(); + // $("#i18n-notice-box").addClass("hidden"); + // } + // else { + // $('#close-i18n-notice-box').on('click', function () { + // $('#i18n-notice-box').hide(); + // $("#i18n-notice-box").addClass("hidden"); + // createCookie('i18nClose', 1); + // }) + // } }) From f1ae19507382d40485760f38aa824c9288227a20 Mon Sep 17 00:00:00 2001 From: Shubham Oulkar Date: Mon, 21 Jul 2025 11:28:30 +0530 Subject: [PATCH 4/9] Fix bug: there was no class hidden in CSS. Notice was hidden by hide(). Now HTML hidden attribute is used to hide notice using CSS. This improves A11y. --- css/style.css | 16 +++++++++------- js/app.js | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/css/style.css b/css/style.css index ce01db8eac..d80ab662be 100644 --- a/css/style.css +++ b/css/style.css @@ -612,6 +612,7 @@ table ul { font-weight: 600; } +/* i18n box */ .doc-notice { padding-block: 1rem; padding-inline: 2.5rem; @@ -619,6 +620,10 @@ table ul { border-radius: 0 6px 6px 0; background: var(--notice-bg); border-left: 3px solid var(--notice-accent); + margin-inline: auto; + margin-block-start: 2rem; + position: relative; + grid-area: i18n; } .doc-notice a{ @@ -626,6 +631,10 @@ table ul { text-decoration: underline; } +.doc-notice[hidden] { + display: none; +} + .doc-info { background: var(--info-bg); border-left: 3px solid var(--info-accent); @@ -646,13 +655,6 @@ table ul { text-decoration: underline; } -#i18n-notice-box { - margin-inline: auto; - margin-block-start: 2rem; - position: relative; - grid-area: i18n; -} - #close-i18n-notice-box { position: absolute; top: 3px; diff --git a/js/app.js b/js/app.js index 2a8980fa35..26d2da3bcb 100644 --- a/js/app.js +++ b/js/app.js @@ -35,7 +35,7 @@ document.addEventListener("DOMContentLoaded", function () { }); // i18n message box - if(readCookie('i18nClose')){ + if(i18nMsgBox && readCookie('i18nClose')){ i18nMsgBox.hidden = true; } else { const closeI18nBtn = document.getElementById("close-i18n-notice-box"); From 3655e8740780255e6ab710c55b518500fdf0134b Mon Sep 17 00:00:00 2001 From: Shubham Oulkar Date: Mon, 21 Jul 2025 12:31:29 +0530 Subject: [PATCH 5/9] fix flashing of i18n notice and improve a11y --- _includes/i18n-notice.html | 2 +- _layouts/home.html | 4 +- _layouts/page.html | 2 +- css/style.css | 5 +- js/app.js | 101 ++++++++++--------------------------- 5 files changed, 35 insertions(+), 79 deletions(-) diff --git a/_includes/i18n-notice.html b/_includes/i18n-notice.html index 05b332413d..6c5adf9e0b 100644 --- a/_includes/i18n-notice.html +++ b/_includes/i18n-notice.html @@ -7,4 +7,4 @@ {% assign notice_link_text = site.data.en.general.i18n_notice_link_text %} {% endif %}

{{ notice }} {{ notice_link_text}}.

-
\ No newline at end of file +
\ No newline at end of file diff --git a/_layouts/home.html b/_layouts/home.html index c5f77e29d7..8d5abafc95 100644 --- a/_layouts/home.html +++ b/_layouts/home.html @@ -14,8 +14,8 @@
{% if page.lang != 'en' %} -
- {% include i18n-notice.html %} + {% endif %} diff --git a/_layouts/page.html b/_layouts/page.html index f86e816d49..0a93f56c84 100644 --- a/_layouts/page.html +++ b/_layouts/page.html @@ -15,7 +15,7 @@
{% if page.lang != 'en' %} -
+ {% endif %} diff --git a/css/style.css b/css/style.css index d80ab662be..2ba687ff8a 100644 --- a/css/style.css +++ b/css/style.css @@ -477,12 +477,12 @@ pre:has(code) button.failed { /* scroll to top button */ -body.scroll #top { +.scroll #top { opacity: .5; display: initial } -body.scroll #top:hover { +.scroll #top:hover { opacity: 1; } @@ -624,6 +624,7 @@ table ul { margin-block-start: 2rem; position: relative; grid-area: i18n; + display: block; } .doc-notice a{ diff --git a/js/app.js b/js/app.js index 26d2da3bcb..e5ffb8aae0 100644 --- a/js/app.js +++ b/js/app.js @@ -34,16 +34,28 @@ document.addEventListener("DOMContentLoaded", function () { } }); - // i18n message box - if(i18nMsgBox && readCookie('i18nClose')){ - i18nMsgBox.hidden = true; - } else { + // i18n message box : this box appears hidden for all page.lang != 'en' + const isI18nCookie = readCookie('i18nClose'); + if(i18nMsgBox && !isI18nCookie) { const closeI18nBtn = document.getElementById("close-i18n-notice-box"); - closeI18nBtn.addEventListener("click", ()=>{ - i18nMsgBox.hidden = true; - createCookie('i18nClose', 1); - }); - } + // show notice box + i18nMsgBox.hidden = false; + // close notice box + if (closeI18nBtn) { + closeI18nBtn.addEventListener("click", ()=>{ + // hide notice + i18nMsgBox.hidden = true; + // set session cookie + createCookie('i18nClose', 1); + }); + // improve keyboard a11y for + closeI18nBtn.addEventListener("keydown", (e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + closeI18nBtn.click(); + } + }); + }}; }); @@ -51,25 +63,6 @@ document.addEventListener("DOMContentLoaded", function () { $(function(){ var doc = $(document); - // top link - // $('#top').click(function(e){ - // $('html, body').animate({scrollTop : 0}, 500); - // return false; - // }); - - // scrolling links - // var added; - // doc.scroll(function(e){ - // if (doc.scrollTop() > 5) { - // if (added) return; - // added = true; - // $('body').addClass('scroll'); - // } else { - // $('body').removeClass('scroll'); - // added = false; - // } - // }) - // menu bar var headings = $('h2, h3').map(function(i, el){ @@ -93,19 +86,6 @@ $(function(){ var parentMenuSelector; var lastApiPrefix; - // if (document.readyState !== 'loading') { - // const languageElement = document.getElementById('languageData'); - // const languagesData = languageElement ? JSON.parse(languageElement.dataset.languages) : []; - - // const langDisplay = document.getElementById('current-lang'); - - // if (langDisplay) { - // const currentLanguage = window.location.pathname.split('/')[1]; - // const matchedLang = languagesData.find(lang => lang.code === currentLanguage); - // langDisplay.textContent = matchedLang ? matchedLang.name : 'English'; - // } - // } - $(document).scroll(function() { var h = closest(); if (!h) return; @@ -126,36 +106,20 @@ $(function(){ lastApiPrefix = currentApiPrefix.split('.')[0]; }) - - // i18n notice - // if (readCookie('i18nClose')) { - // $('#i18n-notice-box').hide(); - // $("#i18n-notice-box").addClass("hidden"); - // } - // else { - // $('#close-i18n-notice-box').on('click', function () { - // $('#i18n-notice-box').hide(); - // $("#i18n-notice-box").addClass("hidden"); - // createCookie('i18nClose', 1); - // }) - // } }) - - function createCookie(name, value, days) { - var expires; - + let expires = ""; if (days) { - var date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); - expires = "; expires=" + date.toGMTString(); - } else { - expires = ""; + const date = new Date(); + date.setTime(date.getTime() + (days * 864e5)); + expires = "; expires=" + date.toUTCString(); } - document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/"; + const secureFlag = location.protocol === "https:" ? "; Secure" : ""; + document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${expires}; path=/; SameSite=Lax${secureFlag}`; } + function readCookie(name) { var nameEQ = encodeURIComponent(name) + "="; var ca = document.cookie.split(';'); @@ -166,12 +130,3 @@ function readCookie(name) { } return null; } - -/* - -Investigate following function - -function eraseCookie(name) { - createCookie(name, "", -1); -} -*/ \ No newline at end of file From 55509385506749ab85a018c9d0bea03ffc6d9034 Mon Sep 17 00:00:00 2001 From: Shubham Oulkar Date: Mon, 21 Jul 2025 18:53:19 +0530 Subject: [PATCH 6/9] add menu highlighting on scroll --- js/app.js | 173 ++++++++++++++++++++++++++---------------------------- 1 file changed, 83 insertions(+), 90 deletions(-) diff --git a/js/app.js b/js/app.js index e5ffb8aae0..77eb314afe 100644 --- a/js/app.js +++ b/js/app.js @@ -13,120 +13,113 @@ document.addEventListener("DOMContentLoaded", function () { } // scroll to top of the page - scrollToTopBtn.addEventListener("click", function (e) { - e.preventDefault(); - window.scrollTo({ - top: 0, - behavior: "smooth" - }) - }); + if (scrollToTopBtn) { + scrollToTopBtn.addEventListener("click", function (e) { + e.preventDefault(); + window.scrollTo({ + top: 0, + behavior: "smooth" + }) + }); + } // add/remove class 'scroll' on scroll by 5px - let added = false; - window.addEventListener('scroll', function () { - if (window.scrollY > 5) { - if (added) return; - added = true; - document.body.classList.add('scroll'); - } else { - document.body.classList.remove('scroll'); - added = false; + const scrollTarget = document.querySelector('.logo-container'); + const scrollObserver = new IntersectionObserver( + ([entry]) => { + if (!entry.isIntersecting) { + document.body.classList.add('scroll'); + } else { + document.body.classList.remove('scroll'); + } + }, + { + root: null, + threshold: 0, + rootMargin: '0px 0px 0px 0px' } - }); + ); + + if (scrollTarget) scrollObserver.observe(scrollTarget); + + // heighlight current Menu on scroll + const headings = Array.from(document.querySelectorAll("h2, h3")); + const menuLinks = document.querySelectorAll("#menu li a"); + + const observerOptions = { + rootMargin: "-200px 0px 0px 0px", + }; + + const menuObserver = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const currentApiPrefix = entry.target.id.split(".")[0]; + const parentMenuSelector = `#${currentApiPrefix}-menu`; + const parentMenuEl = document.querySelector(parentMenuSelector); + + // open submenu on scroll + if (parentMenuEl) parentMenuEl.classList.add("active"); + + // Remove active class from last menu item + const lastActiveMenu = document.querySelector(".active[id$='-menu']"); + if (lastActiveMenu && lastActiveMenu.id !== parentMenuEl.id) { + lastActiveMenu.classList.remove("active"); + } + + // Update active link + menuLinks.forEach((link) => link.classList.remove("active")); + const activeLink = document.querySelector(`a[href="#${entry.target.id}"]`); + if (activeLink) activeLink.classList.add("active"); + } + }); + }, observerOptions); + + headings.forEach((heading) => menuObserver.observe(heading)); // i18n message box : this box appears hidden for all page.lang != 'en' const isI18nCookie = readCookie('i18nClose'); - if(i18nMsgBox && !isI18nCookie) { + if (i18nMsgBox && !isI18nCookie) { const closeI18nBtn = document.getElementById("close-i18n-notice-box"); // show notice box i18nMsgBox.hidden = false; // close notice box if (closeI18nBtn) { - closeI18nBtn.addEventListener("click", ()=>{ + closeI18nBtn.addEventListener("click", () => { // hide notice i18nMsgBox.hidden = true; // set session cookie createCookie('i18nClose', 1); - }); - // improve keyboard a11y for + }); + + // keyboard a11y closeI18nBtn.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); closeI18nBtn.click(); } }); - }}; -}); - - - -$(function(){ - var doc = $(document); - - // menu bar - - var headings = $('h2, h3').map(function(i, el){ - return { - top: $(el).offset().top - 200, - id: el.id } - }); - - function closest() { - var h; - var top = $(window).scrollTop(); - var i = headings.length; - while (i--) { - h = headings[i]; - if (top >= h.top) return h; + }; + + function createCookie(name, value, days) { + let expires = ""; + if (days) { + const date = new Date(); + date.setTime(date.getTime() + (days * 864e5)); + expires = "; expires=" + date.toUTCString(); } + document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${expires}; path=/; SameSite=Lax; Secure`; } - var currentApiPrefix; - var parentMenuSelector; - var lastApiPrefix; - - $(document).scroll(function() { - var h = closest(); - if (!h) return; - - currentApiPrefix = h.id.split('.')[0]; - parentMenuSelector = '#'+ currentApiPrefix + '-menu'; - - $(parentMenuSelector).addClass('active'); - - if (lastApiPrefix && (lastApiPrefix != currentApiPrefix)) { - $('#'+ lastApiPrefix + '-menu').removeClass('active'); + function readCookie(name) { + const nameEQ = encodeURIComponent(name) + "="; + const ca = document.cookie.split(';'); + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) === ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length)); } - - $('#menu li a').removeClass('active'); - - var a = $('a[href="#' + h.id + '"]'); - a.addClass('active'); - - lastApiPrefix = currentApiPrefix.split('.')[0]; - }) -}) - -function createCookie(name, value, days) { - let expires = ""; - if (days) { - const date = new Date(); - date.setTime(date.getTime() + (days * 864e5)); - expires = "; expires=" + date.toUTCString(); + return null; } - const secureFlag = location.protocol === "https:" ? "; Secure" : ""; - document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${expires}; path=/; SameSite=Lax${secureFlag}`; -} - - -function readCookie(name) { - var nameEQ = encodeURIComponent(name) + "="; - var ca = document.cookie.split(';'); - for (var i = 0; i < ca.length; i++) { - var c = ca[i]; - while (c.charAt(0) === ' ') c = c.substring(1, c.length); - if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length)); - } - return null; -} + +}); From c327d43be63208fbcabf8dc7aca15a2394de1b51 Mon Sep 17 00:00:00 2001 From: Shubham Oulkar Date: Mon, 21 Jul 2025 19:28:07 +0530 Subject: [PATCH 7/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20remove=20jquery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _includes/head.html | 1 - js/app.js | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/_includes/head.html b/_includes/head.html index b808c3a3ad..5fec1688b4 100644 --- a/_includes/head.html +++ b/_includes/head.html @@ -65,7 +65,6 @@ {% else %} {% endif %} - diff --git a/js/app.js b/js/app.js index 77eb314afe..c8a98871e6 100644 --- a/js/app.js +++ b/js/app.js @@ -47,7 +47,9 @@ document.addEventListener("DOMContentLoaded", function () { const menuLinks = document.querySelectorAll("#menu li a"); const observerOptions = { - rootMargin: "-200px 0px 0px 0px", + root: null, + rootMargin: "-25% 0px -50px 0px", + threshold: 1, }; const menuObserver = new IntersectionObserver((entries) => { From 5b03669bf44b85f2164715a0a52cf93dbbaa6799 Mon Sep 17 00:00:00 2001 From: shubham oulkar <91728992+ShubhamOulkar@users.noreply.github.com> Date: Thu, 24 Jul 2025 07:35:25 +0530 Subject: [PATCH 8/9] defer app.js Co-authored-by: Sebastian Beltran --- _includes/head.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/head.html b/_includes/head.html index 5fec1688b4..474cca0c80 100644 --- a/_includes/head.html +++ b/_includes/head.html @@ -65,7 +65,7 @@ {% else %} {% endif %} - + From 3a51fb76cdebab783d80fc6da09d001cc79c459a Mon Sep 17 00:00:00 2001 From: Shubham Oulkar Date: Thu, 24 Jul 2025 09:17:56 +0530 Subject: [PATCH 9/9] remove DOMContentLoaded and fix root margin to 25% --- js/app.js | 235 +++++++++++++++++++++++++++--------------------------- 1 file changed, 116 insertions(+), 119 deletions(-) diff --git a/js/app.js b/js/app.js index c8a98871e6..ea24dc2f4c 100644 --- a/js/app.js +++ b/js/app.js @@ -1,127 +1,124 @@ -document.addEventListener("DOMContentLoaded", function () { - const languageElement = document.getElementById('languageData'); - const languagesData = languageElement ? JSON.parse(languageElement.dataset.languages) : []; - const langDisplay = document.getElementById('current-lang'); - const i18nMsgBox = document.getElementById("i18n-notice-box"); - const scrollToTopBtn = document.getElementById("top"); - - // display current language in language picker component - if (langDisplay) { - const currentLanguage = window.location.pathname.split('/')[1]; - const matchedLang = languagesData.find(lang => lang.code === currentLanguage); - langDisplay.textContent = matchedLang ? matchedLang.name : 'English'; - } - - // scroll to top of the page - if (scrollToTopBtn) { - scrollToTopBtn.addEventListener("click", function (e) { - e.preventDefault(); - window.scrollTo({ - top: 0, - behavior: "smooth" - }) - }); - } - - // add/remove class 'scroll' on scroll by 5px - const scrollTarget = document.querySelector('.logo-container'); - const scrollObserver = new IntersectionObserver( - ([entry]) => { - if (!entry.isIntersecting) { - document.body.classList.add('scroll'); - } else { - document.body.classList.remove('scroll'); - } - }, - { - root: null, - threshold: 0, - rootMargin: '0px 0px 0px 0px' +const languageElement = document.getElementById('languageData'); +const languagesData = languageElement ? JSON.parse(languageElement.dataset.languages) : []; +const langDisplay = document.getElementById('current-lang'); +const i18nMsgBox = document.getElementById("i18n-notice-box"); +const scrollToTopBtn = document.getElementById("top"); + +// display current language in language picker component +if (langDisplay) { + const currentLanguage = window.location.pathname.split('/')[1]; + const matchedLang = languagesData.find(lang => lang.code === currentLanguage); + langDisplay.textContent = matchedLang ? matchedLang.name : 'English'; +} + +// scroll to top of the page +if (scrollToTopBtn) { + scrollToTopBtn.addEventListener("click", function (e) { + e.preventDefault(); + window.scrollTo({ + top: 0, + behavior: "smooth" + }) + }) +} + +// add/remove class 'scroll' on scroll by 5px +const scrollTarget = document.querySelector('.logo-container'); +const scrollObserver = new IntersectionObserver( + ([entry]) => { + if (!entry.isIntersecting) { + document.body.classList.add('scroll'); + } else { + document.body.classList.remove('scroll'); } - ); - - if (scrollTarget) scrollObserver.observe(scrollTarget); - - // heighlight current Menu on scroll - const headings = Array.from(document.querySelectorAll("h2, h3")); - const menuLinks = document.querySelectorAll("#menu li a"); - - const observerOptions = { + }, + { root: null, - rootMargin: "-25% 0px -50px 0px", - threshold: 1, - }; - - const menuObserver = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - const currentApiPrefix = entry.target.id.split(".")[0]; - const parentMenuSelector = `#${currentApiPrefix}-menu`; - const parentMenuEl = document.querySelector(parentMenuSelector); - - // open submenu on scroll - if (parentMenuEl) parentMenuEl.classList.add("active"); - - // Remove active class from last menu item - const lastActiveMenu = document.querySelector(".active[id$='-menu']"); - if (lastActiveMenu && lastActiveMenu.id !== parentMenuEl.id) { - lastActiveMenu.classList.remove("active"); - } - - // Update active link - menuLinks.forEach((link) => link.classList.remove("active")); - const activeLink = document.querySelector(`a[href="#${entry.target.id}"]`); - if (activeLink) activeLink.classList.add("active"); + threshold: 0, + rootMargin: '0px 0px 0px 0px' + } +); + +if (scrollTarget) scrollObserver.observe(scrollTarget); + +// heighlight current Menu on scroll +const headings = Array.from(document.querySelectorAll("h2, h3")); +const menuLinks = document.querySelectorAll("#menu li a"); + +const observerOptions = { + root: null, + rootMargin: "-10% 0px -65% 0px", + threshold: 1, +}; + +const menuObserver = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const currentApiPrefix = entry.target.id.split(".")[0]; + const parentMenuSelector = `#${currentApiPrefix}-menu`; + const parentMenuEl = document.querySelector(parentMenuSelector); + + // open submenu on scroll + if (parentMenuEl) parentMenuEl.classList.add("active"); + + // Remove active class from last menu item + const lastActiveMenu = document.querySelector(".active[id$='-menu']"); + if (lastActiveMenu && lastActiveMenu.id !== parentMenuEl.id) { + lastActiveMenu.classList.remove("active"); } - }); - }, observerOptions); - - headings.forEach((heading) => menuObserver.observe(heading)); - // i18n message box : this box appears hidden for all page.lang != 'en' - const isI18nCookie = readCookie('i18nClose'); - if (i18nMsgBox && !isI18nCookie) { - const closeI18nBtn = document.getElementById("close-i18n-notice-box"); - // show notice box - i18nMsgBox.hidden = false; - // close notice box - if (closeI18nBtn) { - closeI18nBtn.addEventListener("click", () => { - // hide notice - i18nMsgBox.hidden = true; - // set session cookie - createCookie('i18nClose', 1); - }); - - // keyboard a11y - closeI18nBtn.addEventListener("keydown", (e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - closeI18nBtn.click(); - } - }); + // Update active link + menuLinks.forEach((link) => link.classList.remove("active")); + const activeLink = document.querySelector(`a[href="#${entry.target.id}"]`); + if (activeLink && !activeLink.classList.contains("active")) activeLink.classList.add("active"); } - }; + }); +}, observerOptions); + +headings.forEach((heading) => menuObserver.observe(heading)); + +// i18n message box : this box appears hidden for all page.lang != 'en' +const isI18nCookie = readCookie('i18nClose'); +if (i18nMsgBox && !isI18nCookie) { + const closeI18nBtn = document.getElementById("close-i18n-notice-box"); + // show notice box + i18nMsgBox.hidden = false; + // close notice box + if (closeI18nBtn) { + closeI18nBtn.addEventListener("click", () => { + // hide notice + i18nMsgBox.hidden = true; + // set session cookie + createCookie('i18nClose', 1); + }); - function createCookie(name, value, days) { - let expires = ""; - if (days) { - const date = new Date(); - date.setTime(date.getTime() + (days * 864e5)); - expires = "; expires=" + date.toUTCString(); - } - document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${expires}; path=/; SameSite=Lax; Secure`; + // keyboard a11y + closeI18nBtn.addEventListener("keydown", (e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + closeI18nBtn.click(); + } + }); } - - function readCookie(name) { - const nameEQ = encodeURIComponent(name) + "="; - const ca = document.cookie.split(';'); - for (var i = 0; i < ca.length; i++) { - var c = ca[i]; - while (c.charAt(0) === ' ') c = c.substring(1, c.length); - if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length)); - } - return null; +}; + +function createCookie(name, value, days) { + let expires = ""; + if (days) { + const date = new Date(); + date.setTime(date.getTime() + (days * 864e5)); + expires = "; expires=" + date.toUTCString(); } - -}); + document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${expires}; path=/; SameSite=Lax; Secure`; +} + +function readCookie(name) { + const nameEQ = encodeURIComponent(name) + "="; + const ca = document.cookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) === ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length)); + } + return null; +}