my personal site
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Enhance accessibility features across portfolio pages, including a new accessibility menu for theme and text size adjustments, and implement skip link for improved navigation. Update styles for accessibility elements and ensure compatibility with system color schemes.

+749 -120
+2
.vscode/settings.json
··· 1 + { 2 + }
+39 -16
index.html
··· 12 12 } 13 13 </script> 14 14 <link rel="stylesheet" href="style.css" /> 15 + <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=match_case" /> 16 + <style> 17 + .material-symbols-outlined { 18 + font-variation-settings: 19 + 'FILL' 0, 20 + 'wght' 400, 21 + 'GRAD' 0, 22 + 'opsz' 24 23 + } 24 + </style> 15 25 </head> 16 26 <body> 27 + <a href="#main-content" class="skip-link">Skip to main content</a> 17 28 <header> 18 - <nav class="container"> 29 + <nav class="container" role="navigation" aria-label="Main navigation"> 19 30 <a href="index.html" class="brand">Jack Hannon</a> 20 31 <div class="header-controls"> 21 - <ul class="menu"> 22 - <li><a href="index.html" class="active">About</a></li> 23 - <li><a href="projects.html">Projects</a></li> 24 - <li><a href="resume.html">Resume</a></li> 32 + <ul class="menu" role="menubar"> 33 + <li><a href="index.html" class="active" role="menuitem" tabindex="0">About</a></li> 34 + <li><a href="projects.html" role="menuitem" tabindex="0">Projects</a></li> 35 + <li><a href="resume.html" role="menuitem" tabindex="0">Resume</a></li> 25 36 </ul> 26 - <button id="theme-toggle" aria-label="Toggle dark mode"> 27 - <span class="icon-sun" style="display:none;" aria-hidden="true"> 28 - <!-- Sun SVG --> 29 - <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg> 30 - </span> 31 - <span class="icon-moon" style="display:none;" aria-hidden="true"> 32 - <!-- Moon SVG --> 33 - <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 0 0 9.79 9.79z"/></svg> 34 - </span> 35 - </button> 37 + <div class="header-divider"></div> 38 + <span id="accessibility-btn" class="accessibility-icon" aria-label="Accessibility options" aria-haspopup="true" aria-expanded="false" tabindex="0"> 39 + <span class="material-symbols-outlined" aria-hidden="true">match_case</span> 40 + </span> 41 + <div id="accessibility-menu" class="accessibility-menu" style="display:none;" role="menu" aria-label="Accessibility options"> 42 + <div class="access-section"> 43 + <div class="access-label">Theme</div> 44 + <label class="access-radio"><input type="radio" name="theme-mode" value="system" /> System</label> 45 + <label class="access-radio"><input type="radio" name="theme-mode" value="light" /> Light</label> 46 + <label class="access-radio"><input type="radio" name="theme-mode" value="dark" /> Dark</label> 47 + </div> 48 + <div class="access-section"> 49 + <label class="access-toggle"><input type="checkbox" id="high-contrast-toggle" /> High Contrast</label> 50 + </div> 51 + <div class="access-section access-textsize"> 52 + <div class="access-label">Text Size</div> 53 + <button type="button" id="text-decrease" class="textsize-round" aria-label="Decrease text size">–</button> 54 + <span id="text-reset" class="textsize-reset" tabindex="0" role="button" aria-label="Reset text size">Reset</span> 55 + <button type="button" id="text-increase" class="textsize-round" aria-label="Increase text size">+</button> 56 + </div> 57 + </div> 36 58 </div> 37 59 </nav> 38 60 </header> 39 - <main class="container"> 61 + <main class="container" id="main-content"> 62 + <div id="a11y-status" class="visually-hidden" aria-live="polite" aria-atomic="true"></div> 40 63 <section class="intro profile-header"> 41 64 <div class="intro-text profile-header-content"> 42 65 <img src="headshot.jpg" alt="Jack Hannon headshot" class="headshot profile-avatar" />
+39 -16
projects.html
··· 11 11 } 12 12 </script> 13 13 <link rel="stylesheet" href="style.css" /> 14 + <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=match_case" /> 15 + <style> 16 + .material-symbols-outlined { 17 + font-variation-settings: 18 + 'FILL' 0, 19 + 'wght' 400, 20 + 'GRAD' 0, 21 + 'opsz' 24 22 + } 23 + </style> 14 24 </head> 15 25 <body> 26 + <a href="#main-content" class="skip-link">Skip to main content</a> 16 27 <header> 17 - <nav class="container"> 28 + <nav class="container" role="navigation" aria-label="Main navigation"> 18 29 <a href="index.html" class="brand">Jack Hannon</a> 19 30 <div class="header-controls"> 20 - <ul class="menu"> 21 - <li><a href="index.html">About</a></li> 22 - <li><a href="projects.html" class="active">Projects</a></li> 23 - <li><a href="resume.html">Resume</a></li> 31 + <ul class="menu" role="menubar"> 32 + <li><a href="index.html" role="menuitem" tabindex="0">About</a></li> 33 + <li><a href="projects.html" class="active" role="menuitem" tabindex="0">Projects</a></li> 34 + <li><a href="resume.html" role="menuitem" tabindex="0">Resume</a></li> 24 35 </ul> 25 - <button id="theme-toggle" aria-label="Toggle dark mode"> 26 - <span class="icon-sun" style="display:none;" aria-hidden="true"> 27 - <!-- Sun SVG --> 28 - <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg> 29 - </span> 30 - <span class="icon-moon" style="display:none;" aria-hidden="true"> 31 - <!-- Moon SVG --> 32 - <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 0 0 9.79 9.79z"/></svg> 33 - </span> 34 - </button> 36 + <div class="header-divider"></div> 37 + <span id="accessibility-btn" class="accessibility-icon" aria-label="Accessibility options" aria-haspopup="true" aria-expanded="false" tabindex="0"> 38 + <span class="material-symbols-outlined" aria-hidden="true">match_case</span> 39 + </span> 40 + <div id="accessibility-menu" class="accessibility-menu" style="display:none;" role="menu" aria-label="Accessibility options"> 41 + <div class="access-section"> 42 + <div class="access-label">Theme</div> 43 + <label class="access-radio"><input type="radio" name="theme-mode" value="system" /> System</label> 44 + <label class="access-radio"><input type="radio" name="theme-mode" value="light" /> Light</label> 45 + <label class="access-radio"><input type="radio" name="theme-mode" value="dark" /> Dark</label> 46 + </div> 47 + <div class="access-section"> 48 + <label class="access-toggle"><input type="checkbox" id="high-contrast-toggle" /> High Contrast</label> 49 + </div> 50 + <div class="access-section access-textsize"> 51 + <div class="access-label">Text Size</div> 52 + <button type="button" id="text-decrease" class="textsize-round" aria-label="Decrease text size">–</button> 53 + <span id="text-reset" class="textsize-reset" tabindex="0" role="button" aria-label="Reset text size">Reset</span> 54 + <button type="button" id="text-increase" class="textsize-round" aria-label="Increase text size">+</button> 55 + </div> 56 + </div> 35 57 </div> 36 58 </nav> 37 59 </header> 38 - <main class="container projects-main"> 60 + <main class="container projects-main" id="main-content"> 61 + <div id="a11y-status" class="visually-hidden" aria-live="polite" aria-atomic="true"></div> 39 62 <h1>Projects</h1> 40 63 <section> 41 64 <h2>The Allentown Village Initiative <small>(Project Lead, 2019–2021)</small></h2>
+40 -17
resume.html
··· 11 11 } 12 12 </script> 13 13 <link rel="stylesheet" href="style.css" /> 14 + <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=match_case" /> 15 + <style> 16 + .material-symbols-outlined { 17 + font-variation-settings: 18 + 'FILL' 0, 19 + 'wght' 400, 20 + 'GRAD' 0, 21 + 'opsz' 24 22 + } 23 + </style> 14 24 </head> 15 25 <body> 26 + <a href="#main-content" class="skip-link">Skip to main content</a> 16 27 <header> 17 - <nav class="container"> 28 + <nav class="container" role="navigation" aria-label="Main navigation"> 18 29 <a href="index.html" class="brand">Jack Hannon</a> 19 30 <div class="header-controls"> 20 - <ul class="menu"> 21 - <li><a href="index.html">About</a></li> 22 - <li><a href="projects.html">Projects</a></li> 23 - <li><a href="resume.html" class="active">Resume</a></li> 31 + <ul class="menu" role="menubar"> 32 + <li><a href="index.html" role="menuitem" tabindex="0">About</a></li> 33 + <li><a href="projects.html" role="menuitem" tabindex="0">Projects</a></li> 34 + <li><a href="resume.html" class="active" role="menuitem" tabindex="0">Resume</a></li> 24 35 </ul> 25 - <button id="theme-toggle" aria-label="Toggle dark mode"> 26 - <span class="icon-sun" style="display:none;" aria-hidden="true"> 27 - <!-- Sun SVG --> 28 - <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg> 29 - </span> 30 - <span class="icon-moon" style="display:none;" aria-hidden="true"> 31 - <!-- Moon SVG --> 32 - <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 0 0 9.79 9.79z"/></svg> 33 - </span> 34 - </button> 36 + <div class="header-divider"></div> 37 + <span id="accessibility-btn" class="accessibility-icon" aria-label="Accessibility options" aria-haspopup="true" aria-expanded="false" tabindex="0"> 38 + <span class="material-symbols-outlined" aria-hidden="true">match_case</span> 39 + </span> 40 + <div id="accessibility-menu" class="accessibility-menu" style="display:none;" role="menu" aria-label="Accessibility options"> 41 + <div class="access-section"> 42 + <div class="access-label">Theme</div> 43 + <label class="access-radio"><input type="radio" name="theme-mode" value="system" /> System</label> 44 + <label class="access-radio"><input type="radio" name="theme-mode" value="light" /> Light</label> 45 + <label class="access-radio"><input type="radio" name="theme-mode" value="dark" /> Dark</label> 46 + </div> 47 + <div class="access-section"> 48 + <label class="access-toggle"><input type="checkbox" id="high-contrast-toggle" /> High Contrast</label> 49 + </div> 50 + <div class="access-section access-textsize"> 51 + <div class="access-label">Text Size</div> 52 + <button type="button" id="text-decrease" class="textsize-round" aria-label="Decrease text size">–</button> 53 + <span id="text-reset" class="textsize-reset" tabindex="0" role="button" aria-label="Reset text size">Reset</span> 54 + <button type="button" id="text-increase" class="textsize-round" aria-label="Increase text size">+</button> 55 + </div> 56 + </div> 35 57 </div> 36 58 </nav> 37 59 </header> 38 - <main class="container resume-main"> 60 + <main class="container resume-main" id="main-content"> 61 + <div id="a11y-status" class="visually-hidden" aria-live="polite" aria-atomic="true"></div> 39 62 <div class="resume-header"> 40 63 <h1>Resume</h1> 41 - <a href="Jack_Hannon_Marketing_Resume_Web.pdf" class="btn pdf-btn" download>Download PDF</a> 64 + <a href="Jack_Hannon_Marketing_Resume_Web.pdf" class="btn pdf-btn" download aria-label="Download Jack Hannon's resume as PDF">Download PDF</a> 42 65 </div> 43 66 44 67 <h2>Education</h2>
+190 -59
script.js
··· 1 + // Accessibility menu logic 1 2 (function() { 2 - // Theme toggle 3 - const themeToggleBtn = document.getElementById('theme-toggle'); 4 - if (themeToggleBtn) { 5 - const sunIcon = themeToggleBtn.querySelector('.icon-sun'); 6 - const moonIcon = themeToggleBtn.querySelector('.icon-moon'); 7 - function updateThemeIcon() { 8 - if (document.documentElement.classList.contains('dark-theme')) { 9 - sunIcon.style.display = 'block'; 10 - moonIcon.style.display = 'none'; 11 - } else { 12 - sunIcon.style.display = 'none'; 13 - moonIcon.style.display = 'block'; 14 - } 3 + const root = document.documentElement; 4 + const accessibilityBtn = document.getElementById('accessibility-btn'); 5 + const menu = document.getElementById('accessibility-menu'); 6 + let menuOpen = false; 7 + 8 + // Theme radio buttons 9 + const themeRadios = menu ? menu.querySelectorAll('input[name="theme-mode"]') : []; 10 + const themeRadioLabels = menu ? menu.querySelectorAll('.access-radio') : []; 11 + // Add system theme radio if not present 12 + if (menu && !menu.querySelector('input[value="system"]')) { 13 + const systemLabel = document.createElement('label'); 14 + systemLabel.className = 'access-radio'; 15 + const systemRadio = document.createElement('input'); 16 + systemRadio.type = 'radio'; 17 + systemRadio.name = 'theme-mode'; 18 + systemRadio.value = 'system'; 19 + systemLabel.appendChild(systemRadio); 20 + systemLabel.appendChild(document.createTextNode(' System')); 21 + // Insert as first option 22 + const section = menu.querySelector('.access-section'); 23 + if (section) section.insertBefore(systemLabel, section.children[1]); 24 + } 25 + // Re-query radios after possible insertion 26 + const allThemeRadios = menu ? menu.querySelectorAll('input[name="theme-mode"]') : []; 27 + 28 + // High contrast toggle 29 + const highContrastToggle = menu ? menu.querySelector('#high-contrast-toggle') : null; 30 + // Text size buttons 31 + const textIncreaseBtn = menu ? menu.querySelector('#text-increase') : null; 32 + const textDecreaseBtn = menu ? menu.querySelector('#text-decrease') : null; 33 + const textResetBtn = menu ? menu.querySelector('#text-reset') : null; 34 + 35 + // Utility functions 36 + function setTheme(mode) { 37 + root.classList.remove('dark-theme', 'light-theme'); 38 + if (mode === 'dark') { 39 + root.classList.add('dark-theme'); 40 + localStorage.setItem('theme', 'dark'); 41 + } else if (mode === 'light') { 42 + root.classList.add('light-theme'); 43 + localStorage.setItem('theme', 'light'); 44 + } else { 45 + // System: remove explicit theme, use OS preference 46 + localStorage.setItem('theme', 'system'); 47 + } 48 + } 49 + 50 + // Utility to announce status to screen readers 51 + function announceA11yStatus(message) { 52 + const status = document.getElementById('a11y-status'); 53 + if (status) { 54 + status.textContent = ''; 55 + setTimeout(() => { status.textContent = message; }, 10); 15 56 } 16 - updateThemeIcon(); 17 - themeToggleBtn.addEventListener('click', () => { 18 - const html = document.documentElement; 19 - const isDark = html.classList.toggle('dark-theme'); 20 - localStorage.setItem('theme', isDark ? 'dark' : 'light'); 21 - updateThemeIcon(); 22 - }); 23 57 } 24 58 25 - // Contact form placeholder handler 26 - const contactForm = document.getElementById('contact-form'); 27 - if (contactForm) { 28 - contactForm.addEventListener('submit', e => { 29 - e.preventDefault(); 30 - alert('Thank you for your message! (This is a static placeholder form.)'); 31 - contactForm.reset(); 32 - }); 59 + function setHighContrast(enabled) { 60 + if (enabled) { 61 + root.classList.add('high-contrast'); 62 + root.classList.add('dark-theme'); // Always dark in high contrast 63 + // Disable theme radios 64 + allThemeRadios.forEach(radio => { 65 + radio.disabled = true; 66 + }); 67 + themeRadioLabels.forEach(label => { 68 + label.classList.add('disabled'); 69 + }); 70 + announceA11yStatus('High contrast mode enabled.'); 71 + } else { 72 + root.classList.remove('high-contrast'); 73 + // Enable theme radios 74 + allThemeRadios.forEach(radio => { 75 + radio.disabled = false; 76 + }); 77 + themeRadioLabels.forEach(label => { 78 + label.classList.remove('disabled'); 79 + }); 80 + // Restore selected theme 81 + const savedTheme = localStorage.getItem('theme') || 'light'; 82 + setTheme(savedTheme); 83 + // Set radio 84 + allThemeRadios.forEach(radio => { radio.checked = (radio.value === savedTheme); }); 85 + announceA11yStatus('High contrast mode disabled.'); 86 + } 87 + localStorage.setItem('highContrast', enabled ? '1' : '0'); 33 88 } 34 89 35 - // View Transition Navigation 36 - const navLinks = Array.from(document.querySelectorAll('nav .menu a')); 37 - // Tag each link with its index 38 - navLinks.forEach((link, idx) => link.dataset.idx = idx); 90 + function setFontSize(size) { 91 + root.style.setProperty('--base-font-size', size + 'px'); 92 + localStorage.setItem('fontSize', size); 93 + } 39 94 40 - navLinks.forEach(link => { 41 - link.addEventListener('click', e => { 42 - const target = link.getAttribute('href'); 43 - const current = document.querySelector('nav .menu a.active'); 44 - const fromIdx = Number(current?.dataset.idx ?? 0); 45 - const toIdx = Number(link.dataset.idx); 95 + function getFontSize() { 96 + const val = parseInt(getComputedStyle(root).getPropertyValue('--base-font-size'), 10); 97 + return isNaN(val) ? 16 : val; 98 + } 46 99 47 - // Determine slide direction 48 - const forward = toIdx > fromIdx; 49 - document.documentElement.style.setProperty( 50 - '--vt-old-animation', 51 - forward 52 - ? '0.4s ease-in both slide-out-left' 53 - : '0.4s ease-in both slide-out-right' 54 - ); 55 - document.documentElement.style.setProperty( 56 - '--vt-new-animation', 57 - forward 58 - ? '0.4s ease-out both slide-in-right' 59 - : '0.4s ease-out both slide-in-left' 60 - ); 100 + function openMenu() { 101 + menu.style.display = 'flex'; 102 + accessibilityBtn.setAttribute('aria-expanded', 'true'); 103 + menuOpen = true; 104 + // Focus first input for accessibility 105 + setTimeout(() => { 106 + const firstInput = menu.querySelector('input, button'); 107 + if (firstInput) firstInput.focus(); 108 + }, 0); 109 + } 110 + function closeMenu() { 111 + menu.style.display = 'none'; 112 + accessibilityBtn.setAttribute('aria-expanded', 'false'); 113 + menuOpen = false; 114 + } 115 + function toggleMenu() { 116 + menuOpen ? closeMenu() : openMenu(); 117 + } 61 118 62 - // Trigger view transition if supported 63 - if (document.startViewTransition) { 119 + // Event listeners 120 + if (accessibilityBtn) { 121 + accessibilityBtn.addEventListener('click', function(e) { 122 + e.stopPropagation(); 123 + toggleMenu(); 124 + }); 125 + accessibilityBtn.addEventListener('keydown', function(e) { 126 + if (e.key === 'Enter' || e.key === ' ') { 64 127 e.preventDefault(); 65 - document.startViewTransition(() => { 66 - window.location.href = target; 67 - }); 128 + toggleMenu(); 68 129 } 69 - // Otherwise, normal navigation 130 + }); 131 + } 132 + 133 + // Theme radio logic 134 + allThemeRadios.forEach(radio => { 135 + radio.addEventListener('change', function() { 136 + if (this.checked) setTheme(this.value); 70 137 }); 71 138 }); 72 - })(); 139 + 140 + // High contrast toggle logic 141 + highContrastToggle && highContrastToggle.addEventListener('change', function() { 142 + setHighContrast(this.checked); 143 + }); 144 + 145 + // Text size logic 146 + textIncreaseBtn && textIncreaseBtn.addEventListener('click', function() { 147 + let size = getFontSize(); 148 + size = Math.min(size + 2, 28); 149 + setFontSize(size); 150 + }); 151 + textDecreaseBtn && textDecreaseBtn.addEventListener('click', function() { 152 + let size = getFontSize(); 153 + size = Math.max(size - 2, 12); 154 + setFontSize(size); 155 + }); 156 + textResetBtn && textResetBtn.addEventListener('click', function() { 157 + setFontSize(16); 158 + }); 159 + 160 + // Close menu on outside click or Escape 161 + document.addEventListener('click', function(e) { 162 + if (menuOpen && !menu.contains(e.target) && e.target !== accessibilityBtn) { 163 + closeMenu(); 164 + } 165 + }); 166 + document.addEventListener('keydown', function(e) { 167 + if (menuOpen && e.key === 'Escape') { 168 + closeMenu(); 169 + accessibilityBtn && accessibilityBtn.focus(); 170 + } 171 + // Keyboard navigation: arrow keys 172 + if (menuOpen && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) { 173 + const focusables = Array.from(menu.querySelectorAll('input, button')); 174 + const idx = focusables.indexOf(document.activeElement); 175 + let nextIdx = idx; 176 + if (e.key === 'ArrowDown') nextIdx = (idx + 1) % focusables.length; 177 + if (e.key === 'ArrowUp') nextIdx = (idx - 1 + focusables.length) % focusables.length; 178 + focusables[nextIdx].focus(); 179 + e.preventDefault(); 180 + } 181 + }); 182 + 183 + // On load: apply saved theme, high contrast, and font size 184 + (function() { 185 + const highContrast = localStorage.getItem('highContrast') === '1'; 186 + setHighContrast(highContrast); 187 + if (highContrastToggle) highContrastToggle.checked = highContrast; 188 + if (!highContrast) { 189 + const savedTheme = localStorage.getItem('theme'); 190 + if (savedTheme === 'dark' || savedTheme === 'light') { 191 + setTheme(savedTheme); 192 + allThemeRadios.forEach(radio => { radio.checked = (radio.value === savedTheme); }); 193 + } else { 194 + setTheme('system'); 195 + allThemeRadios.forEach(radio => { radio.checked = (radio.value === 'system'); }); 196 + } 197 + } 198 + const savedFontSize = parseInt(localStorage.getItem('fontSize'), 10); 199 + if (savedFontSize && savedFontSize !== 16) { 200 + setFontSize(savedFontSize); 201 + } 202 + })(); 203 + })();
+439 -12
style.css
··· 3 3 /* Color theme variables */ 4 4 --bg-color: #ffffff; 5 5 --text-color: #333333; 6 - --link-color: #444444; /* Changed from blue to neutral gray */ 7 - --link-hover-color: #222222; /* Changed from blue to darker gray */ 6 + --link-color: #444444; 7 + --link-hover-color: #222222; 8 8 --input-bg: #ffffff; 9 9 --input-text: #000000; 10 10 --input-border: #cccccc; 11 - --nav-bg: rgba(255,255,255,0.97); /* Increased opacity */ 12 - --nav-active-bg: #f0f1f3; /* Subtle gray for active nav */ 13 - --nav-shadow: 0 2px 8px rgba(0,0,0,0.06); /* Softer, more natural shadow */ 14 - --nav-border: 1.5px solid #d0d7de; /* Subtle border */ 11 + --nav-bg: rgba(255,255,255,0.97); 12 + --nav-active-bg: #f0f1f3; 13 + --nav-shadow: 0 2px 8px rgba(0,0,0,0.06); 14 + --nav-border: 1.5px solid #d0d7de; 15 15 --btn-bg: #f4f4f7; 16 16 --btn-bg-hover: #e0e0e7; 17 17 --btn-text: #232b36; ··· 24 24 .dark-theme { 25 25 --bg-color: #1a1a1a; 26 26 --text-color: #e0e0e0; 27 - --link-color: #bbbbbb; /* Changed from blue to neutral gray */ 28 - --link-hover-color: #ffffff; /* Changed from blue to white */ 27 + --link-color: #bbbbbb; 28 + --link-hover-color: #ffffff; 29 29 --input-bg: #333333; 30 30 --input-text: #f0f0f0; 31 31 --input-border: #555555; 32 - --nav-bg: rgba(30,30,30,0.98); /* Increased opacity */ 32 + --nav-bg: rgba(30,30,30,0.98); 33 33 --nav-active-bg: #232b36; 34 - --nav-shadow: 0 4px 24px rgba(0,0,0,0.32); /* More prominent shadow */ 35 - --nav-border: 1.5px solid #333a40; /* Subtle border for dark */ 36 - --btn-bg: #1A1A1A; 34 + --nav-shadow: 0 4px 24px rgba(0,0,0,0.32); 35 + --nav-border: 1.5px solid #333a40; 36 + --btn-bg: #232b36; 37 37 --btn-bg-hover: #232b36; 38 38 --btn-text: #fff; 39 39 --btn-active-bg: #fff; ··· 41 41 --btn-active-underline: #fff; 42 42 } 43 43 44 + /* System mode: use the same variables as .dark-theme and :root via media queries */ 45 + @media (prefers-color-scheme: dark) { 46 + :root:not(.light-theme):not(.dark-theme) { 47 + --bg-color: #1a1a1a; 48 + --text-color: #e0e0e0; 49 + --link-color: #bbbbbb; 50 + --link-hover-color: #ffffff; 51 + --input-bg: #333333; 52 + --input-text: #f0f0f0; 53 + --input-border: #555555; 54 + --nav-bg: rgba(30,30,30,0.98); 55 + --nav-active-bg: #232b36; 56 + --nav-shadow: 0 4px 24px rgba(0,0,0,0.32); 57 + --nav-border: 1.5px solid #333a40; 58 + --btn-bg: #232b36; 59 + --btn-bg-hover: #232b36; 60 + --btn-text: #fff; 61 + --btn-active-bg: #fff; 62 + --btn-active-text: #232b36; 63 + --btn-active-underline: #fff; 64 + } 65 + } 66 + @media (prefers-color-scheme: light) { 67 + :root:not(.light-theme):not(.dark-theme) { 68 + --bg-color: #ffffff; 69 + --text-color: #333333; 70 + --link-color: #444444; 71 + --link-hover-color: #222222; 72 + --input-bg: #ffffff; 73 + --input-text: #000000; 74 + --input-border: #cccccc; 75 + --nav-bg: rgba(255,255,255,0.97); 76 + --nav-active-bg: #f0f1f3; 77 + --nav-shadow: 0 2px 8px rgba(0,0,0,0.06); 78 + --nav-border: 1.5px solid #d0d7de; 79 + --btn-bg: #f4f4f7; 80 + --btn-bg-hover: #e0e0e7; 81 + --btn-text: #232b36; 82 + --btn-active-bg: #111; 83 + --btn-active-text: #fff; 84 + --btn-active-underline: #232b36; 85 + } 86 + } 87 + 44 88 /* Global resets/base styles */ 45 89 * { 46 90 box-sizing: border-box; ··· 69 113 gap: 1.2rem; 70 114 } 71 115 116 + /* Add a divider for the right-side controls */ 117 + .header-divider { 118 + width: 1.5px; 119 + height: 2.2rem; 120 + background: #e0e0e7; 121 + margin: 0 0.3rem 0 1.1rem; 122 + align-self: center; 123 + border-radius: 2px; 124 + } 125 + .dark-theme .header-divider { 126 + background: #333a40; 127 + } 128 + 129 + /* Accessibility icon (Aa) styling, no button look */ 130 + .accessibility-icon { 131 + display: flex; 132 + align-items: center; 133 + justify-content: center; 134 + width: 2.8rem; 135 + height: 2.8rem; 136 + font-size: 2rem; 137 + color: var(--text-color); 138 + background: none; 139 + border: none; 140 + border-radius: 50%; 141 + cursor: pointer; 142 + transition: color 0.15s; 143 + outline: none; 144 + margin-left: 0; 145 + } 146 + .accessibility-icon .material-symbols-outlined { 147 + font-size: 2rem; 148 + line-height: 1; 149 + display: flex; 150 + align-items: center; 151 + justify-content: center; 152 + cursor: pointer; 153 + } 154 + .accessibility-icon:focus, .accessibility-icon:hover { 155 + background: none; 156 + color: var(--link-hover-color); 157 + outline: none; 158 + box-shadow: none; 159 + } 160 + .dark-theme .accessibility-icon:focus, .dark-theme .accessibility-icon:hover { 161 + background: none; 162 + color: #fff; 163 + outline: none; 164 + box-shadow: none; 165 + } 166 + 167 + /* Remove button-specific styles from accessibility icon */ 168 + #accessibility-btn { 169 + all: unset; 170 + } 171 + 72 172 /* Header navigation */ 73 173 header { 74 174 margin-top: 2.5rem; ··· 142 242 } 143 243 .dark-theme .menu a.active { 144 244 color: #fff !important; /* Pure white for active tab in dark theme */ 245 + } 246 + /* System mode: active tab color matches system theme */ 247 + @media (prefers-color-scheme: dark) { 248 + :root:not(.light-theme):not(.dark-theme) .menu a.active { 249 + color: #fff !important; 250 + } 251 + } 252 + @media (prefers-color-scheme: light) { 253 + :root:not(.light-theme):not(.dark-theme) .menu a.active { 254 + color: #111 !important; 255 + } 145 256 } 146 257 #theme-toggle { 147 258 margin-left: 1.2rem; ··· 427 538 .projects-main > h1 { 428 539 margin-top: 0; 429 540 } 541 + 542 + /* Accessibility: High Contrast Mode */ 543 + .high-contrast { 544 + --bg-color: #000 !important; 545 + --text-color: #fff !important; 546 + --link-color: #ffff00 !important; 547 + --link-hover-color: #00ffff !important; 548 + --input-bg: #000 !important; 549 + --input-text: #fff !important; 550 + --input-border: #fff !important; 551 + --nav-bg: #000 !important; 552 + --nav-active-bg: #222 !important; 553 + --nav-shadow: none !important; 554 + --nav-border: 2px solid #fff !important; 555 + --btn-bg: #000 !important; 556 + --btn-bg-hover: #222 !important; 557 + --btn-text: #fff !important; 558 + --btn-active-bg: #fff !important; 559 + --btn-active-text: #000 !important; 560 + --btn-active-underline: #fff !important; 561 + } 562 + 563 + /* Accessibility: Font size scaling */ 564 + :root { 565 + --base-font-size: 16px; 566 + } 567 + html, body { 568 + font-size: var(--base-font-size); 569 + transition: background 0.2s, color 0.2s, font-size 0.2s; 570 + } 571 + 572 + /* Accessibility Button (matches theme toggle style) */ 573 + /* 574 + #accessibility-btn { 575 + margin-left: 1.2rem; 576 + display: flex; 577 + align-items: center; 578 + justify-content: center; 579 + width: 2.4rem; 580 + height: 2.4rem; 581 + border: 1.5px solid #c0c0c0; 582 + background: #f7f7f9; 583 + color: var(--text-color); 584 + border-radius: 50%; 585 + cursor: pointer; 586 + transition: background 0.15s, color 0.15s, border 0.15s; 587 + font-size: 1.2rem; 588 + position: relative; 589 + } 590 + #accessibility-btn:hover, #accessibility-btn:focus { 591 + background: #ececf0; 592 + color: #111; 593 + outline: none; 594 + border-color: #888; 595 + } 596 + .dark-theme #accessibility-btn { 597 + border: 1.5px solid #444a55; 598 + background: #23242a; 599 + color: #fff; 600 + } 601 + .dark-theme #accessibility-btn:hover, .dark-theme #accessibility-btn:focus { 602 + background: #2d2e36; 603 + color: #fff; 604 + border-color: #888; 605 + } 606 + */ 607 + .icon-accessibility svg { 608 + display: block; 609 + } 610 + .icon-accessibility { 611 + font-size: 1em; 612 + font-weight: bold; 613 + font-family: inherit; 614 + letter-spacing: 0; 615 + display: flex; 616 + align-items: center; 617 + justify-content: center; 618 + height: 100%; 619 + width: 100%; 620 + } 621 + 622 + /* Accessibility Menu */ 623 + .accessibility-menu { 624 + position: absolute; 625 + right: 0; 626 + top: 3.2rem; 627 + background: var(--nav-bg); 628 + border: var(--nav-border); 629 + box-shadow: var(--nav-shadow); 630 + border-radius: 1.2rem; 631 + padding: 0.7rem 1.1rem; 632 + z-index: 1000; 633 + min-width: 210px; 634 + display: flex; 635 + flex-direction: column; 636 + gap: 0.7rem; 637 + font-size: 1em; 638 + } 639 + .access-section { 640 + display: flex; 641 + flex-direction: column; 642 + gap: 0.3rem; 643 + margin-bottom: 0.2rem; 644 + } 645 + .access-label { 646 + font-weight: 600; 647 + font-size: 1em; 648 + margin-bottom: 0.1em; 649 + color: var(--text-color); 650 + opacity: 0.8; 651 + } 652 + .access-radio { 653 + display: flex; 654 + align-items: center; 655 + gap: 0.5em; 656 + font-size: 1em; 657 + cursor: pointer; 658 + margin-bottom: 0.1em; 659 + } 660 + .access-radio input[type="radio"] { 661 + accent-color: var(--link-color); 662 + margin-right: 0.4em; 663 + } 664 + .access-toggle { 665 + display: flex; 666 + align-items: center; 667 + gap: 0.5em; 668 + font-size: 1em; 669 + cursor: pointer; 670 + } 671 + .access-toggle input[type="checkbox"] { 672 + accent-color: var(--link-color); 673 + margin-right: 0.4em; 674 + } 675 + .access-textsize { 676 + flex-direction: row; 677 + align-items: center; 678 + justify-content: center; 679 + gap: 0.5em; 680 + } 681 + .access-textsize .access-label { 682 + margin-right: 0.7em; 683 + margin-bottom: 0; 684 + } 685 + .access-textsize button { 686 + background: var(--btn-bg); 687 + color: var(--btn-text); 688 + border: 1px solid #333; 689 + border-radius: 6px; 690 + font-weight: 600; 691 + font-size: 1em; 692 + font-family: inherit; 693 + cursor: pointer; 694 + padding: 0.2em 0.7em; 695 + margin: 0 0.1em; 696 + transition: background 0.15s, color 0.15s, border 0.15s; 697 + } 698 + .access-textsize button:hover, .access-textsize button:focus { 699 + background: var(--btn-bg-hover); 700 + color: var(--btn-text); 701 + border-color: #444; 702 + outline: none; 703 + } 704 + .access-textsize button:active { 705 + background: var(--btn-active-bg); 706 + color: var(--btn-active-text); 707 + border-color: #222; 708 + } 709 + .access-radio input[disabled] { 710 + opacity: 0.5; 711 + pointer-events: none; 712 + } 713 + .access-radio.disabled { 714 + opacity: 0.5; 715 + pointer-events: none; 716 + cursor: not-allowed; 717 + } 718 + 719 + /* Ensure menu is above nav */ 720 + .header-controls { 721 + position: relative; 722 + } 723 + 724 + /* Responsive: accessibility menu */ 725 + @media (max-width: 600px) { 726 + .accessibility-menu { 727 + right: 0.5rem; 728 + top: 3.2rem; 729 + min-width: 140px; 730 + padding: 0.3rem 0.3rem; 731 + } 732 + .access-section { 733 + gap: 0.2rem; 734 + } 735 + .access-textsize button { 736 + font-size: 0.95em; 737 + padding: 0.15em 0.5em; 738 + } 739 + } 740 + 741 + .textsize-round { 742 + width: 2.1rem; 743 + height: 2.1rem; 744 + border-radius: 50%; 745 + border: 1.5px solid #c0c0c0; 746 + background: #f7f7f9; 747 + color: var(--text-color); 748 + font-size: 1.1em; 749 + font-weight: bold; 750 + display: inline-flex; 751 + align-items: center; 752 + justify-content: center; 753 + margin: 0 0.1em; 754 + cursor: pointer; 755 + transition: background 0.15s, color 0.15s, border 0.15s; 756 + } 757 + .textsize-round:hover, .textsize-round:focus { 758 + background: #ececf0; 759 + color: #111; 760 + border-color: #888; 761 + outline: none; 762 + } 763 + .dark-theme .textsize-round { 764 + border: 1.5px solid #444a55; 765 + background: #23242a; 766 + color: #fff; 767 + } 768 + .dark-theme .textsize-round:hover, .dark-theme .textsize-round:focus { 769 + background: #2d2e36; 770 + color: #fff; 771 + border-color: #888; 772 + } 773 + .textsize-reset { 774 + display: inline-flex; 775 + align-items: center; 776 + justify-content: center; 777 + background: none; 778 + border: none; 779 + color: var(--link-color); 780 + font-size: 1em; 781 + font-weight: 500; 782 + margin: 0 0.2em; 783 + cursor: pointer; 784 + text-decoration: none; 785 + padding: 0 0.3em; 786 + min-width: 2.1rem; 787 + min-height: 2.1rem; 788 + } 789 + .textsize-reset:focus, .textsize-reset:hover { 790 + color: var(--link-hover-color); 791 + outline: none; 792 + text-decoration: none; 793 + } 794 + 795 + /* Visually hidden utility for screen readers */ 796 + .visually-hidden { 797 + position: absolute !important; 798 + width: 1px !important; 799 + height: 1px !important; 800 + padding: 0 !important; 801 + margin: -1px !important; 802 + overflow: hidden !important; 803 + clip: rect(0, 0, 0, 0) !important; 804 + white-space: nowrap !important; 805 + border: 0 !important; 806 + } 807 + 808 + /* Skip link styles */ 809 + .skip-link { 810 + position: absolute; 811 + left: 0; 812 + top: 0; 813 + background: #ffff00; 814 + color: #000; 815 + padding: 0.7em 1.2em; 816 + z-index: 2000; 817 + font-weight: bold; 818 + border-radius: 0 0 8px 0; 819 + transform: translateY(-120%); 820 + transition: transform 0.2s; 821 + outline: 2px solid #000; 822 + text-decoration: none; 823 + } 824 + .skip-link:focus, .skip-link:active { 825 + transform: translateY(0); 826 + outline: 4px solid #00ffff; 827 + background: #000; 828 + color: #ffff00; 829 + } 830 + 831 + /* High contrast focus outlines for all interactive elements */ 832 + .high-contrast a, 833 + .high-contrast button, 834 + .high-contrast input, 835 + .high-contrast .btn, 836 + .high-contrast .menu a, 837 + .high-contrast .pdf-btn, 838 + .high-contrast [role="menuitem"], 839 + .high-contrast [tabindex="0"] { 840 + outline: 3px solid #ffff00 !important; 841 + outline-offset: 2px !important; 842 + box-shadow: none !important; 843 + } 844 + .high-contrast a:focus, 845 + .high-contrast button:focus, 846 + .high-contrast input:focus, 847 + .high-contrast .btn:focus, 848 + .high-contrast .menu a:focus, 849 + .high-contrast .pdf-btn:focus, 850 + .high-contrast [role="menuitem"]:focus, 851 + .high-contrast [tabindex="0"]:focus { 852 + outline: 4px solid #00ffff !important; 853 + outline-offset: 2px !important; 854 + background: #222 !important; 855 + color: #ffff00 !important; 856 + }