forked from colin/resume
Fix CSP issues by moving inline script to external file and adding SRI
This commit is contained in:
parent
0fbd77f073
commit
ac3d30d597
|
@ -26,7 +26,7 @@
|
|||
Cross-Origin-Opener-Policy "same-origin"
|
||||
|
||||
# Simplified CSP for static content
|
||||
Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none';"
|
||||
Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'; require-sri-for script;"
|
||||
}
|
||||
|
||||
# Handle 404s
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<meta name="description" content="Colin Knapp - Cybersecurity Expert and Software Developer Portfolio">
|
||||
<title>Colin Knapp Portfolio</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="theme.js" integrity="sha384-UbjhZypK/bO7YAIsVbI8lGTyMauZwXy3snXwqT5jddKFsEt2YIDpSqcvTHn7Fn0e" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="theme-switch">
|
||||
|
@ -182,68 +183,5 @@
|
|||
|
||||
<p class="accessibility-notice"><strong>Accessibility:</strong> This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
const html = document.documentElement;
|
||||
|
||||
// Check for saved theme preference, default to auto
|
||||
const savedTheme = localStorage.getItem('theme') || 'auto';
|
||||
updateTheme(savedTheme);
|
||||
|
||||
function updateTheme(theme) {
|
||||
// Update button state and labels
|
||||
const themeLabels = {
|
||||
light: 'Theme mode: Light',
|
||||
dark: 'Theme mode: Dark',
|
||||
auto: 'Theme mode: Auto'
|
||||
};
|
||||
|
||||
themeToggle.setAttribute('aria-label', themeLabels[theme]);
|
||||
themeToggle.setAttribute('aria-pressed', theme !== 'auto');
|
||||
|
||||
// Update button icon
|
||||
const themeIcons = {
|
||||
light: '🌞',
|
||||
dark: '🌙',
|
||||
auto: '🌓'
|
||||
};
|
||||
|
||||
themeToggle.textContent = themeIcons[theme];
|
||||
|
||||
if (theme === 'auto') {
|
||||
html.removeAttribute('data-theme');
|
||||
} else {
|
||||
html.setAttribute('data-theme', theme);
|
||||
}
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('click', () => {
|
||||
const currentTheme = html.getAttribute('data-theme') || 'auto';
|
||||
let newTheme;
|
||||
|
||||
switch(currentTheme) {
|
||||
case 'light':
|
||||
newTheme = 'dark';
|
||||
break;
|
||||
case 'dark':
|
||||
newTheme = 'auto';
|
||||
break;
|
||||
default:
|
||||
newTheme = 'light';
|
||||
}
|
||||
|
||||
updateTheme(newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
});
|
||||
|
||||
// Handle keyboard navigation
|
||||
themeToggle.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
themeToggle.click();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,62 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
const html = document.documentElement;
|
||||
|
||||
// Check for saved theme preference, default to auto
|
||||
const savedTheme = localStorage.getItem('theme') || 'auto';
|
||||
updateTheme(savedTheme);
|
||||
|
||||
function updateTheme(theme) {
|
||||
// Update button state and labels
|
||||
const themeLabels = {
|
||||
light: 'Theme mode: Light',
|
||||
dark: 'Theme mode: Dark',
|
||||
auto: 'Theme mode: Auto'
|
||||
};
|
||||
|
||||
themeToggle.setAttribute('aria-label', themeLabels[theme]);
|
||||
themeToggle.setAttribute('aria-pressed', theme !== 'auto');
|
||||
|
||||
// Update button icon
|
||||
const themeIcons = {
|
||||
light: '🌞',
|
||||
dark: '🌙',
|
||||
auto: '🌓'
|
||||
};
|
||||
|
||||
themeToggle.textContent = themeIcons[theme];
|
||||
|
||||
if (theme === 'auto') {
|
||||
html.removeAttribute('data-theme');
|
||||
} else {
|
||||
html.setAttribute('data-theme', theme);
|
||||
}
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('click', () => {
|
||||
const currentTheme = html.getAttribute('data-theme') || 'auto';
|
||||
let newTheme;
|
||||
|
||||
switch(currentTheme) {
|
||||
case 'light':
|
||||
newTheme = 'dark';
|
||||
break;
|
||||
case 'dark':
|
||||
newTheme = 'auto';
|
||||
break;
|
||||
default:
|
||||
newTheme = 'light';
|
||||
}
|
||||
|
||||
updateTheme(newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
});
|
||||
|
||||
// Handle keyboard navigation
|
||||
themeToggle.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
themeToggle.click();
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue