/** * PortfolioJournal Main JavaScript * Handles dark mode, navigation, and dynamic content */ (function() { 'use strict'; // Dark Mode Management const DarkMode = { STORAGE_KEY: 'pj-theme', init() { const saved = localStorage.getItem(this.STORAGE_KEY); const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if (saved === 'dark' || (!saved && prefersDark)) { document.documentElement.setAttribute('data-theme', 'dark'); } else { document.documentElement.setAttribute('data-theme', 'light'); } // Listen for system preference changes window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { if (!localStorage.getItem(this.STORAGE_KEY)) { document.documentElement.setAttribute('data-theme', e.matches ? 'dark' : 'light'); } }); }, toggle() { const current = document.documentElement.getAttribute('data-theme'); const next = current === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', next); localStorage.setItem(this.STORAGE_KEY, next); this.updateToggleButton(); }, updateToggleButton() { const btn = document.querySelector('.theme-toggle'); if (btn) { const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; btn.setAttribute('aria-label', isDark ? 'Switch to light mode' : 'Switch to dark mode'); btn.innerHTML = isDark ? this.getSunIcon() : this.getMoonIcon(); } }, getSunIcon() { return ``; }, getMoonIcon() { return ``; } }; // Navigation Management const Navigation = { init() { this.setActiveLink(); this.setupMobileMenu(); this.setupKeyboardNav(); }, setActiveLink() { const currentPath = window.location.pathname; const links = document.querySelectorAll('.nav-link'); links.forEach(link => { const href = link.getAttribute('href'); const isActive = currentPath.endsWith(href) || (href === 'index.html' && (currentPath === '/' || currentPath.endsWith('/'))); if (isActive) { link.classList.add('active'); link.setAttribute('aria-current', 'page'); } }); }, setupMobileMenu() { const toggle = document.querySelector('.mobile-menu-toggle'); const nav = document.querySelector('.nav-links'); if (toggle && nav) { toggle.addEventListener('click', () => { const isOpen = nav.classList.toggle('open'); toggle.setAttribute('aria-expanded', isOpen); toggle.innerHTML = isOpen ? this.getCloseIcon() : this.getMenuIcon(); }); // Close menu on link click nav.querySelectorAll('a').forEach(link => { link.addEventListener('click', () => { nav.classList.remove('open'); toggle.setAttribute('aria-expanded', 'false'); toggle.innerHTML = this.getMenuIcon(); }); }); } }, setupKeyboardNav() { document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { const nav = document.querySelector('.nav-links'); const toggle = document.querySelector('.mobile-menu-toggle'); if (nav && nav.classList.contains('open')) { nav.classList.remove('open'); toggle.setAttribute('aria-expanded', 'false'); toggle.innerHTML = this.getMenuIcon(); } } }); }, getMenuIcon() { return ``; }, getCloseIcon() { return ``; } }; // Dynamic Content const Content = { init() { this.insertYear(); this.insertConfig(); this.insertEffectiveDate(); }, insertYear() { document.querySelectorAll('[data-year]').forEach(el => { el.textContent = CONFIG.COPYRIGHT_YEAR; }); }, insertConfig() { document.querySelectorAll('[data-config]').forEach(el => { const key = el.getAttribute('data-config'); if (CONFIG[key]) { if (el.tagName === 'A') { el.href = CONFIG[key]; } else { el.textContent = CONFIG[key]; } } }); }, insertEffectiveDate() { document.querySelectorAll('[data-effective-date]').forEach(el => { const dateValue = CONFIG.EFFECTIVE_DATE === 'today' ? new Date() : new Date(CONFIG.EFFECTIVE_DATE); const date = Number.isNaN(dateValue.getTime()) ? new Date() : dateValue; el.textContent = date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); }); } }; // Smooth Scroll const SmoothScroll = { init() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', (e) => { const targetId = anchor.getAttribute('href'); if (targetId === '#') return; const target = document.querySelector(targetId); if (target) { e.preventDefault(); target.scrollIntoView({ behavior: 'smooth', block: 'start' }); target.focus({ preventScroll: true }); } }); }); } }; // Cookie Notice (only if analytics enabled) const CookieNotice = { STORAGE_KEY: 'pj-cookies-accepted', init() { if (!CONFIG.ENABLE_ANALYTICS) return; if (localStorage.getItem(this.STORAGE_KEY)) return; this.show(); }, show() { const notice = document.createElement('div'); notice.className = 'cookie-notice'; notice.innerHTML = ` `; document.body.appendChild(notice); }, accept() { localStorage.setItem(this.STORAGE_KEY, 'true'); this.hide(); // Initialize analytics here if needed }, decline() { localStorage.setItem(this.STORAGE_KEY, 'false'); this.hide(); }, hide() { const notice = document.querySelector('.cookie-notice'); if (notice) notice.remove(); } }; // FAQ Accordion const FAQ = { init() { document.querySelectorAll('.faq-question').forEach(question => { question.addEventListener('click', () => { const item = question.parentElement; const isOpen = item.classList.contains('open'); // Close all others document.querySelectorAll('.faq-item.open').forEach(openItem => { openItem.classList.remove('open'); openItem.querySelector('.faq-question').setAttribute('aria-expanded', 'false'); }); // Toggle current if (!isOpen) { item.classList.add('open'); question.setAttribute('aria-expanded', 'true'); } }); // Keyboard support question.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); question.click(); } }); }); } }; // Initialize everything on DOM ready document.addEventListener('DOMContentLoaded', () => { DarkMode.init(); DarkMode.updateToggleButton(); Navigation.init(); Content.init(); SmoothScroll.init(); FAQ.init(); CookieNotice.init(); // Setup theme toggle button const themeToggle = document.querySelector('.theme-toggle'); if (themeToggle) { themeToggle.addEventListener('click', () => DarkMode.toggle()); } }); // Expose necessary functions globally window.DarkMode = DarkMode; window.CookieNotice = CookieNotice; })();