Spaces:
Running
Running
| (function () { | |
| const qs = (sel, ctx = document) => ctx.querySelector(sel); | |
| const qsa = (sel, ctx = document) => Array.from(ctx.querySelectorAll(sel)); | |
| // Mobile menu | |
| const menuToggle = qs('#menuToggle'); | |
| const mobileMenu = qs('#mobileMenu'); | |
| if (menuToggle && mobileMenu) { | |
| menuToggle.addEventListener('click', () => { | |
| const isHidden = mobileMenu.classList.contains('hidden'); | |
| mobileMenu.classList.toggle('hidden', !isHidden); | |
| menuToggle.setAttribute('aria-expanded', String(isHidden)); | |
| }); | |
| } | |
| // Expand/Collapse all curriculum modules | |
| const expandBtn = qs('#expandAll'); | |
| const modules = qsa('.module'); | |
| if (expandBtn && modules.length) { | |
| const toggleAll = () => { | |
| const shouldOpen = expandBtn.dataset.state !== 'open'; | |
| modules.forEach((mod) => { | |
| const content = qs('.module-content', mod); | |
| const icon = qs('.module-icon', mod); | |
| const summaryBtn = qs('.module-toggle', mod); | |
| if (shouldOpen) { | |
| openModule(mod, content, icon, summaryBtn, false); | |
| } else { | |
| closeModule(mod, content, icon, summaryBtn, false); | |
| } | |
| }); | |
| expandBtn.dataset.state = shouldOpen ? 'open' : 'closed'; | |
| const icon = qs('i', expandBtn); | |
| if (icon) { | |
| icon.setAttribute('data-feather', shouldOpen ? 'minus-square' : 'plus-square'); | |
| // Replace icon | |
| if (window.feather && typeof feather.replace === 'function') { | |
| feather.replace(); | |
| } | |
| } | |
| }; | |
| expandBtn.addEventListener('click', toggleAll); | |
| } | |
| // Individual module toggle | |
| function openModule(module, content, icon, summaryBtn, animate = true) { | |
| summaryBtn.setAttribute('aria-expanded', 'true'); | |
| content.classList.remove('hidden'); | |
| content.classList.add('open'); | |
| if (icon) icon.style.transform = 'rotate(180deg)'; | |
| if (animate) { | |
| // auto height animation | |
| content.style.maxHeight = '0px'; | |
| requestAnimationFrame(() => { | |
| content.style.maxHeight = content.scrollHeight + 'px'; | |
| }); | |
| const onEnd = (e) => { | |
| if (e.propertyName !== 'max-height') return; | |
| content.style.maxHeight = 'none'; | |
| content.removeEventListener('transitionend', onEnd); | |
| }; | |
| content.addEventListener('transitionend', onEnd); | |
| } else { | |
| content.style.maxHeight = 'none'; | |
| } | |
| } | |
| function closeModule(module, content, icon, summaryBtn, animate = true) { | |
| summaryBtn.setAttribute('aria-expanded', 'false'); | |
| if (animate) { | |
| // from current height to 0 | |
| content.style.maxHeight = content.scrollHeight + 'px'; | |
| requestAnimationFrame(() => { | |
| content.style.maxHeight = '0px'; | |
| }); | |
| const onEnd = (e) => { | |
| if (e.propertyName !== 'max-height') return; | |
| content.classList.remove('open'); | |
| content.classList.add('hidden'); | |
| content.style.maxHeight = ''; | |
| content.removeEventListener('transitionend', onEnd); | |
| }; | |
| content.addEventListener('transitionend', onEnd); | |
| } else { | |
| content.classList.remove('open'); | |
| content.classList.add('hidden'); | |
| content.style.maxHeight = ''; | |
| } | |
| if (icon) icon.style.transform = 'rotate(0deg)'; | |
| } | |
| modules.forEach((mod) => { | |
| const content = qs('.module-content', mod); | |
| const icon = qs('.module-icon', mod); | |
| const summaryBtn = qs('.module-toggle', mod); | |
| summaryBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| const isOpen = summaryBtn.getAttribute('aria-expanded') === 'true'; | |
| if (isOpen) { | |
| closeModule(mod, content, icon, summaryBtn, true); | |
| } else { | |
| openModule(mod, content, icon, summaryBtn, true); | |
| } | |
| }); | |
| }); | |
| // FAQ accordion behavior: only one open at a time | |
| const faqs = qsa('#faq details'); | |
| if (faqs.length) { | |
| faqs.forEach((faq) => { | |
| faq.addEventListener('toggle', () => { | |
| if (faq.open) { | |
| faqs.forEach((other) => { | |
| if (other !== faq) other.removeAttribute('open'); | |
| }); | |
| } | |
| }); | |
| }); | |
| } | |
| // Smooth scroll offset for fixed header | |
| const offset = 16; // header height-ish | |
| qsa('a[href^="#"]').forEach((a) => { | |
| a.addEventListener('click', (e) => { | |
| const id = a.getAttribute('href'); | |
| if (!id || id === '#') return; | |
| const el = qs(id); | |
| if (!el) return; | |
| e.preventDefault(); | |
| const top = el.getBoundingClientRect().top + window.scrollY - offset; | |
| window.scrollTo({ top, behavior: 'smooth' }); | |
| history.pushState(null, '', id); | |
| }); | |
| }); | |
| // Footer year | |
| const yearEl = qs('#year'); | |
| if (yearEl) { | |
| yearEl.textContent = String(new Date().getFullYear()); | |
| } | |
| // Replace Feather icons if dynamically inserted | |
| if (window.feather && typeof feather.replace === 'function') { | |
| feather.replace(); | |
| } | |
| })(); |