Files
C1024_2026/assets/js/theme.js
T

67 lines
2.8 KiB
JavaScript

(() => {
// Cycle the persisted preference; CSS resolves "auto" from the system setting.
const root = document.documentElement;
const colorModes = ['auto', 'light', 'dark'];
const storedTheme = localStorage.getItem('c1024-theme');
let colorMode = colorModes.includes(storedTheme) ? storedTheme : 'auto';
root.dataset.theme = colorMode;
const themeToggle = document.querySelector('[data-theme-toggle]');
if (themeToggle) {
const themeIcon = themeToggle.querySelector('[data-theme-icon]');
const labels = { auto: 'Automatic', light: 'Light', dark: 'Dark' };
const icons = { auto: '#icon-auto', light: '#icon-light', dark: '#icon-dark' };
const updateThemeControl = () => {
const nextMode = colorModes[(colorModes.indexOf(colorMode) + 1) % colorModes.length];
themeToggle.setAttribute('aria-label', `Color mode: ${labels[colorMode]}. Activate to use ${labels[nextMode]} mode.`);
themeIcon.setAttribute('href', icons[colorMode]);
};
updateThemeControl();
themeToggle.addEventListener('click', () => {
colorMode = colorModes[(colorModes.indexOf(colorMode) + 1) % colorModes.length];
root.dataset.theme = colorMode;
localStorage.setItem('c1024-theme', colorMode);
updateThemeControl();
});
}
// Hide the sticky header only while scrolling down past its own height; batch scroll work per frame.
const header = document.querySelector('.site-header');
let lastY = window.scrollY;
let ticking = false;
window.addEventListener('scroll', () => {
if (ticking || !header) return;
ticking = true;
requestAnimationFrame(() => {
const currentY = window.scrollY;
header.classList.toggle('is-hidden', currentY > lastY && currentY > header.offsetHeight);
lastY = currentY;
ticking = false;
});
}, { passive: true });
// Keep the search panel accessible and close it when focus moves outside its controls.
const searchToggle = document.querySelector('[data-search-toggle]');
const searchPanel = document.querySelector('[data-search-panel]');
if (searchToggle && searchPanel) {
searchToggle.addEventListener('click', () => {
const open = searchPanel.classList.toggle('is-open');
searchToggle.setAttribute('aria-expanded', String(open));
if (open) searchPanel.querySelector('input')?.focus();
});
document.addEventListener('click', event => {
if (!searchPanel.contains(event.target) && !searchToggle.contains(event.target)) {
searchPanel.classList.remove('is-open');
searchToggle.setAttribute('aria-expanded', 'false');
}
});
}
// The mobile trigger controls the header class consumed by the responsive navigation CSS.
const menuToggle = document.querySelector('[data-menu-toggle]');
if (menuToggle && header) menuToggle.addEventListener('click', () => {
const open = header.classList.toggle('menu-open');
menuToggle.setAttribute('aria-expanded', String(open));
});
})();