187 lines
6.5 KiB
JavaScript
187 lines
6.5 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
|
initTheme();
|
|||
|
|
initFooterYear();
|
|||
|
|
initMobileMenu();
|
|||
|
|
fetchPageData();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/* -------------------------------------------------------------------------- */
|
|||
|
|
/* Theme / Dark Mode Management */
|
|||
|
|
/* -------------------------------------------------------------------------- */
|
|||
|
|
function initTheme() {
|
|||
|
|
const isDarkStored = localStorage.theme === 'dark';
|
|||
|
|
const noThemeStored = !('theme' in localStorage);
|
|||
|
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|||
|
|
|
|||
|
|
if (isDarkStored || (noThemeStored && prefersDark)) {
|
|||
|
|
document.documentElement.classList.add('dark');
|
|||
|
|
} else {
|
|||
|
|
document.documentElement.classList.remove('dark');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
updateThemeUI();
|
|||
|
|
|
|||
|
|
document.querySelectorAll('.theme-toggle').forEach(button => {
|
|||
|
|
button.addEventListener('click', toggleDarkMode);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function toggleDarkMode() {
|
|||
|
|
const isDark = document.documentElement.classList.toggle('dark');
|
|||
|
|
localStorage.theme = isDark ? 'dark' : 'light';
|
|||
|
|
updateThemeUI();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function updateThemeUI() {
|
|||
|
|
const isDark = document.documentElement.classList.contains('dark');
|
|||
|
|
|
|||
|
|
document.querySelectorAll('.icon-sun').forEach(el => el.classList.toggle('hidden', !isDark));
|
|||
|
|
document.querySelectorAll('.icon-moon').forEach(el => el.classList.toggle('hidden', isDark));
|
|||
|
|
|
|||
|
|
document.querySelectorAll('.theme-text').forEach(el => {
|
|||
|
|
el.textContent = isDark ? '☀️ Light Mode' : '🌙 Dark Mode';
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* -------------------------------------------------------------------------- */
|
|||
|
|
/* Footer Year Handler */
|
|||
|
|
/* -------------------------------------------------------------------------- */
|
|||
|
|
function initFooterYear() {
|
|||
|
|
const yearEl = document.getElementById('current-year');
|
|||
|
|
if (yearEl) {
|
|||
|
|
yearEl.textContent = new Date().getFullYear();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* -------------------------------------------------------------------------- */
|
|||
|
|
/* Mobile Menu Toggle */
|
|||
|
|
/* -------------------------------------------------------------------------- */
|
|||
|
|
function initMobileMenu() {
|
|||
|
|
const menuToggleBtn = document.getElementById('menu-toggle');
|
|||
|
|
const mobileMenu = document.getElementById('mobile-menu');
|
|||
|
|
const iconHamburger = document.getElementById('icon-hamburger');
|
|||
|
|
const iconClose = document.getElementById('icon-close');
|
|||
|
|
|
|||
|
|
if (!menuToggleBtn || !mobileMenu) return;
|
|||
|
|
|
|||
|
|
menuToggleBtn.setAttribute('aria-expanded', 'false');
|
|||
|
|
|
|||
|
|
menuToggleBtn.addEventListener('click', () => {
|
|||
|
|
const isHidden = mobileMenu.classList.toggle('hidden');
|
|||
|
|
menuToggleBtn.setAttribute('aria-expanded', String(!isHidden));
|
|||
|
|
iconHamburger.classList.toggle('hidden', !isHidden);
|
|||
|
|
iconClose.classList.toggle('hidden', isHidden);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* -------------------------------------------------------------------------- */
|
|||
|
|
/* Async Data Fetching (Pages, Banner & Exam Schedule) */
|
|||
|
|
/* -------------------------------------------------------------------------- */
|
|||
|
|
async function fetchPageData() {
|
|||
|
|
const currentPath = window.location.pathname;
|
|||
|
|
|
|||
|
|
// 1. Fetch Banner Data
|
|||
|
|
try {
|
|||
|
|
const bannerResp = await fetch(`/assets/banner.json?t=${Date.now()}`);
|
|||
|
|
if (bannerResp.ok) {
|
|||
|
|
const banner = await bannerResp.json();
|
|||
|
|
if (banner && banner.title && banner.content) {
|
|||
|
|
document.getElementById('banner-title').textContent = banner.title;
|
|||
|
|
document.getElementById('banner-content').textContent = banner.content;
|
|||
|
|
document.getElementById('banner').classList.remove('hidden');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('Failed to load banner data:', err);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. Fetch Navigation Links
|
|||
|
|
try {
|
|||
|
|
const navResp = await fetch('/assets/pages.json');
|
|||
|
|
if (navResp.ok) {
|
|||
|
|
const pages = await navResp.json();
|
|||
|
|
renderNavigation(pages, currentPath);
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('Failed to load navigation data:', err);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. Fetch Exam Schedule (only runs if the #exams-list element exists)
|
|||
|
|
const examsContainer = document.getElementById('exams-list');
|
|||
|
|
if (examsContainer) {
|
|||
|
|
try {
|
|||
|
|
const examResp = await fetch(`/assets/exams.json?t=${Date.now()}`);
|
|||
|
|
if (examResp.ok) {
|
|||
|
|
const exams = await examResp.json();
|
|||
|
|
renderExams(exams, examsContainer);
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('Failed to load exam data:', err);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. Fetch Net Check-In Data (only runs on pages containing #callsigns-list or #weekly-list)
|
|||
|
|
const callsignsContainer = document.getElementById('callsigns-list');
|
|||
|
|
const weeklyContainer = document.getElementById('weekly-list');
|
|||
|
|
|
|||
|
|
if (callsignsContainer || weeklyContainer) {
|
|||
|
|
try {
|
|||
|
|
const netResp = await fetch('/net/net.json');
|
|||
|
|
if (netResp.ok) {
|
|||
|
|
const data = await netResp.json();
|
|||
|
|
renderNetStats(data, callsignsContainer, weeklyContainer);
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('Failed to load net statistics data:', err);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderNavigation(pages, currentPath) {
|
|||
|
|
const desktopContainer = document.getElementById('desktop-links');
|
|||
|
|
const mobileContainer = document.getElementById('mobile-links');
|
|||
|
|
|
|||
|
|
if (!desktopContainer || !mobileContainer) return;
|
|||
|
|
|
|||
|
|
desktopContainer.innerHTML = '';
|
|||
|
|
mobileContainer.innerHTML = '';
|
|||
|
|
|
|||
|
|
pages.forEach(page => {
|
|||
|
|
const desktopLink = document.createElement('a');
|
|||
|
|
desktopLink.href = page.path;
|
|||
|
|
desktopLink.textContent = page.name;
|
|||
|
|
if (currentPath === page.path) desktopLink.classList.add('active');
|
|||
|
|
desktopContainer.appendChild(desktopLink);
|
|||
|
|
|
|||
|
|
const mobileLink = document.createElement('a');
|
|||
|
|
mobileLink.href = page.path;
|
|||
|
|
mobileLink.textContent = page.name;
|
|||
|
|
if (currentPath === page.path) mobileLink.classList.add('active');
|
|||
|
|
mobileContainer.appendChild(mobileLink);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderExams(exams, container) {
|
|||
|
|
container.innerHTML = '';
|
|||
|
|
|
|||
|
|
if (Array.isArray(exams) && exams.length > 0) {
|
|||
|
|
exams.forEach(exam => {
|
|||
|
|
const li = document.createElement('li');
|
|||
|
|
li.append(`${exam.date}, ${exam.time} – ${exam.location} – `);
|
|||
|
|
const link = document.createElement('a');
|
|||
|
|
link.href = exam.register;
|
|||
|
|
link.className = 'link';
|
|||
|
|
link.target = '_blank';
|
|||
|
|
link.rel = 'noopener';
|
|||
|
|
link.textContent = 'Register here';
|
|||
|
|
li.appendChild(link);
|
|||
|
|
container.appendChild(li);
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
const li = document.createElement('li');
|
|||
|
|
li.textContent = "We don't have any sessions scheduled right now, but if you're ready to test let us know and we'll set one up!";
|
|||
|
|
container.appendChild(li);
|
|||
|
|
}
|
|||
|
|
}
|