class ThemeManager {
constructor() {
this.themeKey = 'lazyllama-theme';
this.themes = ['auto', 'light', 'dark'];
this.currentTheme = this.getStoredTheme() || 'auto';
this.init();
}
init() {
this.applyTheme(this.currentTheme);
this.setupThemeToggle();
this.setupSystemThemeListener();
this.updateThemeIcon();
}
getStoredTheme() {
try {
return localStorage.getItem(this.themeKey);
} catch (e) {
console.warn('LocalStorage not available, using default theme');
return null;
}
}
storeTheme(theme) {
try {
localStorage.setItem(this.themeKey, theme);
} catch (e) {
console.warn('Cannot store theme preference');
}
}
applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
this.currentTheme = theme;
this.storeTheme(theme);
}
getNextTheme() {
const currentIndex = this.themes.indexOf(this.currentTheme);
const nextIndex = (currentIndex + 1) % this.themes.length;
return this.themes[nextIndex];
}
getSystemTheme() {
if (typeof window !== 'undefined' && window.matchMedia) {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
return 'light';
}
getEffectiveTheme() {
if (this.currentTheme === 'auto') {
return this.getSystemTheme();
}
return this.currentTheme;
}
updateThemeIcon() {
const themeIcon = document.querySelector('.theme-icon');
const themeToggle = document.querySelector('.theme-toggle');
if (!themeIcon || !themeToggle) return;
const effectiveTheme = this.getEffectiveTheme();
const icons = {
light: '🌙',
dark: '☀️'
};
const iconToShow = effectiveTheme === 'dark' ? icons.dark : icons.light;
themeIcon.textContent = iconToShow;
const tooltips = {
auto: 'Switch to light theme',
light: 'Switch to dark theme',
dark: 'Switch to auto theme'
};
themeToggle.setAttribute('title', tooltips[this.currentTheme]);
themeToggle.setAttribute('aria-label', tooltips[this.currentTheme]);
}
setupThemeToggle() {
const themeToggle = document.getElementById('themeToggle');
if (!themeToggle) {
console.warn('Theme toggle button not found');
return;
}
themeToggle.addEventListener('click', () => {
const nextTheme = this.getNextTheme();
this.applyTheme(nextTheme);
this.updateThemeIcon();
themeToggle.style.transform = 'scale(0.95)';
setTimeout(() => {
themeToggle.style.transform = '';
}, 150);
});
themeToggle.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
themeToggle.click();
}
});
}
setupSystemThemeListener() {
if (typeof window === 'undefined' || !window.matchMedia) return;
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleSystemThemeChange = (e) => {
if (this.currentTheme === 'auto') {
this.updateThemeIcon();
this.announceThemeChange();
}
};
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', handleSystemThemeChange);
}
else if (mediaQuery.addListener) {
mediaQuery.addListener(handleSystemThemeChange);
}
}
announceThemeChange() {
const announcement = document.createElement('div');
announcement.setAttribute('aria-live', 'polite');
announcement.setAttribute('aria-atomic', 'true');
announcement.className = 'sr-only';
announcement.style.cssText = `
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
`;
const effectiveTheme = this.getEffectiveTheme();
announcement.textContent = `Theme switched to ${effectiveTheme} mode`;
document.body.appendChild(announcement);
setTimeout(() => {
document.body.removeChild(announcement);
}, 1000);
}
setTheme(theme) {
if (this.themes.includes(theme)) {
this.applyTheme(theme);
this.updateThemeIcon();
} else {
console.warn(`Invalid theme: ${theme}. Available themes:`, this.themes);
}
}
getThemeInfo() {
return {
current: this.currentTheme,
effective: this.getEffectiveTheme(),
available: this.themes,
system: this.getSystemTheme()
};
}
}
class SmoothScroll {
constructor() {
this.offset = 80; this.init();
}
init() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', (e) => {
e.preventDefault();
const targetId = anchor.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
this.scrollToElement(targetElement);
}
});
});
}
scrollToElement(element) {
const elementPosition = element.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - this.offset;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
}
}
class PerformanceEnhancements {
constructor() {
this.init();
}
init() {
this.setupLazyLoading();
this.setupScrollAnimations();
this.preloadResources();
}
setupLazyLoading() {
if ('IntersectionObserver' in window) {
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
imageObserver.unobserve(img);
}
});
});
images.forEach(img => imageObserver.observe(img));
}
}
setupScrollAnimations() {
if ('IntersectionObserver' in window) {
const animatedElements = document.querySelectorAll('.feature-card, .link-card');
const animationObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
});
animatedElements.forEach(el => {
el.style.opacity = '0';
el.style.transform = 'translateY(20px)';
el.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
animationObserver.observe(el);
});
}
}
preloadResources() {
}
}
document.addEventListener('DOMContentLoaded', () => {
window.themeManager = new ThemeManager();
new SmoothScroll();
new PerformanceEnhancements();
document.body.classList.add('loaded');
});
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
document.body.classList.add('page-hidden');
} else {
document.body.classList.remove('page-hidden');
}
});
if (typeof module !== 'undefined' && module.exports) {
module.exports = { ThemeManager, SmoothScroll, PerformanceEnhancements };
}