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 {
wednesday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - todayDay + 10);
}
const dynamicDate = wednesday.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });
const 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) {
const carouselWrapper = carousel.querySelector(".faculty-carousel-wrapper1");
const cards = carouselWrapper
? carouselWrapper.querySelectorAll(".faculty-member-card, .tcgi-related-product-item")
: [];
const dotsContainer = carousel.querySelector(".dots-container");
const prevArrow = carousel.querySelector(".prevarrow1");
const nextArrow = carousel.querySelector(".nextarrow1");
const counterElement = carousel.querySelector(".cg-carousel-counter-number");
if (!carouselWrapper || !cards.length || !dotsContainer || !prevArrow || !nextArrow) return;
let currentIndex = 0;
let isDragging = false;
let startPos = 0;
let currentTranslate = 0;
let prevTranslate = 0;
let animationID = 0;
let totalSlides = cards.length;
function updateCounter() {
// E.g. "1 / 2"
counterElement.textContent = `${currentIndex + 1} / ${totalSlides}`;
}
function calculateDimensions() {
const cardStyle = window.getComputedStyle(cards[0]);
const cardWidth = cards[0].offsetWidth + parseFloat(cardStyle.marginRight);
const cardsPerViewport = Math.max(1, Math.floor(carousel.offsetWidth / cardWidth));
const totalGroups = Math.ceil(cards.length / cardsPerViewport);
return { cardWidth, cardsPerViewport, totalGroups };
}
function createDots(totalGroups) {
dotsContainer.innerHTML = "";
for (let i = 0; i dot.classList.remove("active"));
if (dots[currentIndex]) {
dots[currentIndex].classList.add("active");
}
}
}
function setCarouselPosition() {
carouselWrapper.style.transform = `translateX(${currentTranslate}px)`;
}
function moveCarousel(cardWidth, cardsPerViewport, totalGroups) {
const maxIndex = totalGroups - 1;
currentIndex = Math.max(0, Math.min(currentIndex, maxIndex));
const translateX = -(currentIndex * cardsPerViewport * cardWidth);
currentTranslate = translateX;
prevTranslate = translateX;
carouselWrapper.style.transition = "transform 0.6s ease-in-out";
setCarouselPosition();
updateDots(totalGroups);
updateCounter();
}
function animation() {
setCarouselPosition();
if (isDragging) requestAnimationFrame(animation);
}
function getPositionX(event) {
return event.type.includes("mouse") ? event.pageX : event.touches[0].clientX;
}
function startDrag(event) {
isDragging = true;
startPos = getPositionX(event);
animationID = requestAnimationFrame(animation);
carouselWrapper.style.transition = "none";
carouselWrapper.style.cursor = "grabbing";
document.body.style.userSelect = "none";
}
function drag(event) {
if (!isDragging) return;
const currentPosition = getPositionX(event);
currentTranslate = prevTranslate + currentPosition - startPos;
setCarouselPosition();
}
function endDrag() {
if (!isDragging) return;
isDragging = false;
cancelAnimationFrame(animationID);
const movedBy = currentTranslate - prevTranslate;
const { cardWidth, cardsPerViewport, totalGroups } = calculateDimensions();
if (movedBy < -100 && currentIndex 100 && currentIndex > 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();
});