document.addEventListener("DOMContentLoaded", function () { // ACCORDION TOGGLE document.querySelectorAll(".toggle-arrow").forEach(function (btn) { btn.addEventListener("click", function () { handleAccordionToggle(btn); }); btn.addEventListener("keydown", function (e) { if (e.key === "Enter" || e.key === " ") { handleAccordionToggle(btn); e.preventDefault(); } }); }); function handleAccordionToggle(header) { // Find the closest .modules-tab or .course-details__modules-content ancestor const container = header.closest(".modules-tab, .course-details__modules-content"); const panelId = header.getAttribute("aria-controls"); const panel = document.getElementById(panelId); const expanded = header.getAttribute("aria-expanded") === "true"; // Collapse all toggles and panels within this container only if (container) { container.querySelectorAll(".toggle-arrow").forEach((h) => { h.setAttribute("aria-expanded", "false"); h.querySelector("i").classList.remove("fa-chevron-up"); h.querySelector("i").classList.add("fa-chevron-down"); }); container.querySelectorAll(".additional-content").forEach((p) => { p.style.display = "none"; }); } // Expand this one if not already expanded if (!expanded) { header.setAttribute("aria-expanded", "true"); header.querySelector("i").classList.remove("fa-chevron-down"); header.querySelector("i").classList.add("fa-chevron-up"); if (panel) panel.style.display = "block"; } } // MODULE TABS (ARIA) WITH SUPPORT FOR NESTED TABS function setupTabs(tabSelector, panelSelector) { // Always use a flat NodeList of all tabs for keyboard navigation const modules = typeof tabSelector === "string" ? document.querySelectorAll(tabSelector) : tabSelector; const panels = typeof panelSelector === "string" ? document.querySelectorAll(panelSelector) : panelSelector; function activateTab(tab) { // Find the closest tablist to scope tab/panel matching const tablist = tab.closest('.course-details__nav, .sidebar, [role="tablist"]'); // Only use tabs within this tablist for activation, but for keyboard navigation use all modules const localTabs = tablist ? tablist.querySelectorAll('[role="tab"]') : modules; // Find all panels in the same container let container = tablist; // If tablist is .sidebar, go up to .dashboard for correct panel scope if (container && container.classList.contains("sidebar") && container.closest(".dashboard")) { container = container.closest(".dashboard"); } else if ( container && container.classList.contains("course-details__nav") && container.closest(".tab-container") ) { container = container.closest(".tab-container"); } else if (!container) { container = document; } const localPanels = container.querySelectorAll('[role="tabpanel"]'); localTabs.forEach((mod) => { mod.setAttribute("aria-selected", "false"); mod.setAttribute("tabindex", "-1"); mod.classList.remove("active"); }); localPanels.forEach((panel) => { panel.style.display = "none"; panel.setAttribute("aria-hidden", "true"); }); tab.setAttribute("aria-selected", "true"); tab.setAttribute("tabindex", "0"); tab.classList.add("active"); let contentSelector = tab.getAttribute("data-content"); // If selector doesn't start with #, add # if (contentSelector && !contentSelector.startsWith("#")) contentSelector = "#" + contentSelector; // Scope panel lookup to the same container as localPanels const panel = container.querySelector(contentSelector); if (panel) { panel.style.display = "block"; panel.setAttribute("aria-hidden", "false"); // If this panel contains nested tabs, activate the first nested tab const nestedTablist = panel.querySelector('[role="tablist"]'); if (nestedTablist) { const nestedTabs = nestedTablist.querySelectorAll('[role="tab"]'); if (nestedTabs.length) { // Find the tab with .active or fallback to first let nestedInitial = Array.from(nestedTabs).find((t) => t.classList.contains("active")) || nestedTabs[0]; if (nestedInitial) { nestedInitial.click(); } } } } } modules.forEach((tab) => { tab.addEventListener("click", (e) => { e.preventDefault(); activateTab(tab); }); tab.addEventListener("keydown", (e) => { // Use the flat modules NodeList for navigation, not just the local tablist let idx = Array.prototype.indexOf.call(modules, tab); if (e.key === "ArrowRight" || e.key === "ArrowDown") { e.preventDefault(); let nextIdx = (idx + 1) % modules.length; modules[nextIdx].focus(); activateTab(modules[nextIdx]); } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") { e.preventDefault(); let prevIdx = (idx - 1 + modules.length) % modules.length; modules[prevIdx].focus(); activateTab(modules[prevIdx]); } else if (e.key === "Home") { e.preventDefault(); modules[0].focus(); activateTab(modules[0]); } else if (e.key === "End") { e.preventDefault(); modules[modules.length - 1].focus(); activateTab(modules[modules.length - 1]); } else if (e.key === "Enter" || e.key === " ") { e.preventDefault(); activateTab(tab); } }); }); // Set initial aria-hidden panels.forEach((panel) => { panel.setAttribute("aria-hidden", panel.style.display === "block" ? "false" : "true"); }); // Activate the tab that already has the 'active' class, or fallback to the first tab let initialTab = Array.from(modules).find((tab) => tab.classList.contains("active")) || modules[0]; if (initialTab) activateTab(initialTab); } // Setup main tabs (flat NodeList for navigation) setupTabs( document.querySelectorAll('.course-details__nav .module[role="tab"]'), document.querySelectorAll('.course-details__modules > .content[role="tabpanel"]') ); // Setup desktop payment tabs document.querySelectorAll(".dashboard.payment-options.desktop").forEach(function (dashboard) { const tabs = dashboard.querySelectorAll('.sidebar .module[role="tab"]'); const panels = dashboard.querySelectorAll('.module-content > .content[role="tabpanel"]'); if (tabs.length && panels.length) { setupTabs(tabs, panels); } }); // Setup curriculum desktop tabs document.querySelectorAll(".dashboard.curriculm.desktop").forEach(function (dashboard) { const tabs = dashboard.querySelectorAll('.sidebar .module[role="tab"]'); const panels = dashboard.querySelectorAll('.module-content > .content[role="tabpanel"]'); if (tabs.length && panels.length) { setupTabs(tabs, panels); } }); // DROPDOWN ACCESSIBILITY document.querySelectorAll(".dropdown .method_heading, .dropdown h4").forEach((dropdown) => { dropdown.addEventListener("click", function (event) { event.stopPropagation(); const dropdownContent = this.nextElementSibling; dropdownContent.style.display = dropdownContent.style.display === "none" || dropdownContent.style.display === "" ? "block" : "none"; this.querySelector("i").classList.toggle("fa-chevron-down", dropdownContent.style.display === "none"); this.querySelector("i").classList.toggle("fa-chevron-up", dropdownContent.style.display === "block"); }); }); // HERO CAROUSEL const slideContainer = document.querySelector(".carousel-slide"); const slides = document.querySelectorAll(".testimonial"); const slideContainerWidth = document.querySelector(".carousel-container")?.clientWidth || 0; let slideIndex = 0; const totalSlides = slides.length; if (slideContainer && slides.length) { slides.forEach((slide) => { const clone = slide.cloneNode(true); slideContainer.appendChild(clone); }); function showNextSlide() { slideIndex++; if (slideIndex >= totalSlides) { slideIndex = 0; slideContainer.style.transition = "none"; slideContainer.style.transform = "translateX(0)"; requestAnimationFrame(() => { requestAnimationFrame(() => { slideContainer.style.transition = "transform 10s linear"; slideContainer.style.transform = `translateX(-${slideContainerWidth * slideIndex}px)`; }); }); } else { slideContainer.style.transform = `translateX(-${slideContainerWidth * slideIndex}px)`; } } slideContainer.style.transition = "transform 10s linear"; showNextSlide(); setInterval(showNextSlide, 10000); } // UPCOMING PROGRAMME START DATES const today = new Date(); let wednesday; const todayDay = today.getDay(); if (todayDay <= 3) { wednesday=new date(today.getfullyear(), today.getmonth(), today.getdate() - todayday + 3); } else 10); const dynamicdate=wednesday.toLocaleDateString("en-US", year: "numeric", month: "long", day: "numeric" }); dynamicdateelem=document.getElementById("dynamic-date"); if (dynamicdateelem) dynamicdateelem.textcontent=dynamicDate; // jquery-dependent scripts make carousel logic work for every .carousel panel on the page document.queryselectorall(".carousel-wrapper").foreach(function (carousel) carouselwrapper=carousel.querySelector(".faculty-carousel-wrapper1"); cards=carouselWrapper ? carouselwrapper.queryselectorall(".faculty-member-card, .tcgi-related-product-item") : []; dotscontainer=carousel.querySelector(".dots-container"); prevarrow=carousel.querySelector(".prevarrow1"); nextarrow=carousel.querySelector(".nextarrow1"); counterelement=carousel.querySelector(".cg-carousel-counter-number"); (!carouselwrapper || !cards.length !dotscontainer !prevarrow !nextarrow) return; let currentindex=0; isdragging=false; startpos=0; currenttranslate=0; prevtranslate=0; animationid=0; totalslides=cards.length; function updatecounter() e.g. "1 / 2" counterelement.textcontent=`${currentIndex 1} ${totalslides}`; calculatedimensions() cardstyle=window.getComputedStyle(cards[0]); cardwidth=cards[0].offsetWidth parsefloat(cardstyle.marginright); cardsperviewport=Math.max(1, math.floor(carousel.offsetwidth cardwidth)); totalgroups=Math.ceil(cards.length cardsperviewport); return cardwidth, cardsperviewport, }; createdots(totalgroups) dotscontainer.innerhtml="" ; (let i=0; dot.classlist.remove("active")); (dots[currentindex]) dots[currentindex].classlist.add("active"); setcarouselposition() carouselwrapper.style.transform=`translateX(${currentTranslate}px)`; movecarousel(cardwidth, totalgroups) maxindex=totalGroups 1; math.min(currentindex, maxindex)); translatex=-(currentIndex * cardwidth); carouselwrapper.style.transition="transform 0.6s ease-in-out" setcarouselposition(); updatedots(totalgroups); updatecounter(); animation() (isdragging) requestanimationframe(animation); getpositionx(event) event.type.includes("mouse") event.pagex event.touches[0].clientx; startdrag(event) carouselwrapper.style.cursor="grabbing" document.body.style.userselect="none" drag(event) (!isdragging) currentposition=getPositionX(event); startpos; enddrag() cancelanimationframe(animationid); movedby=currentTranslate prevtranslate; (movedby < -100 && 100> 0) currentIndex -= 1; moveCarousel(cardWidth, cardsPerViewport, totalGroups); document.body.style.userSelect = "auto"; carouselWrapper.style.cursor = "grab"; } function addDragListeners() { carousel.addEventListener("mousedown", startDrag); carousel.addEventListener("mousemove", drag); carousel.addEventListener("mouseup", endDrag); carousel.addEventListener("mouseleave", endDrag); carousel.addEventListener("touchstart", startDrag, { passive: true }); carousel.addEventListener("touchmove", drag, { passive: true }); carousel.addEventListener("touchend", endDrag); } function initCarousel() { // Helper to check if carousel is visible function isVisible(elem) { return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); } if (counterElement) counterElement.textContent = `${currentIndex + 1} / ${totalSlides}`; function safeInit() { if (!isVisible(carousel)) { // Try again when the carousel becomes visible const observer = new MutationObserver(() => { if (isVisible(carousel)) { observer.disconnect(); safeInit(); } }); observer.observe(document.body, { attributes: true, childList: true, subtree: true }); return; } const { cardWidth, cardsPerViewport, totalGroups } = calculateDimensions(); const dots = createDots(totalGroups); prevArrow.addEventListener("click", () => { currentIndex -= 1; moveCarousel(cardWidth, cardsPerViewport, totalGroups); }); nextArrow.addEventListener("click", () => { currentIndex += 1; moveCarousel(cardWidth, cardsPerViewport, totalGroups); }); dots.forEach((dot, i) => { dot.addEventListener("click", () => { currentIndex = i; moveCarousel(cardWidth, cardsPerViewport, totalGroups); }); }); window.addEventListener("resize", () => { const { cardWidth, cardsPerViewport, totalGroups } = calculateDimensions(); createDots(totalGroups); totalSlides = cards.length; // recalculate in case of DOM changes moveCarousel(cardWidth, cardsPerViewport, totalGroups); updateCounter(); }); addDragListeners(); moveCarousel(cardWidth, cardsPerViewport, totalGroups); updateCounter(); } safeInit(); } initCarousel(); });=>