schmelczer-dev/src/components/Header.astro
2026-05-11 21:30:57 +01:00

128 lines
3.7 KiB
Text

---
import { navItems, site } from '../lib/site';
const current = Astro.url.pathname;
// Exact match for the current page; section match (descendant URLs) for
// ancestor links. `aria-current="page"` is reserved for the exact page,
// `"true"` indicates an ancestor section.
function currentState(href: string): 'page' | 'true' | undefined {
if (current === href) return 'page';
if (href !== '/' && current.startsWith(href)) return 'true';
return undefined;
}
// Header shows nav items except Home and footer-only entries. RSS lives as a
// dedicated icon link to the right of the nav.
const headerNavItems = navItems.filter((item) => item.href !== '/' && !item.footerOnly);
---
<a class="skip-link" href="#content">Skip to content</a>
<header class="site-header">
<a class="site-title" href="/" aria-current={currentState('/')}>{site.name}</a>
<div class="header-actions">
<nav class="site-nav" aria-label="Primary">
{
headerNavItems.map((item) => (
<a href={item.href} aria-current={currentState(item.href)}>
{item.label}
</a>
))
}
</nav>
<a class="rss-link" href="/rss.xml" aria-label="RSS feed">
<svg
class="rss-icon"
viewBox="0 0 24 24"
width="18"
height="18"
aria-hidden="true"
focusable="false"
>
<path
fill="currentColor"
d="M6.18 17.82a2.18 2.18 0 1 1-4.36 0 2.18 2.18 0 0 1 4.36 0ZM2 9.86v3.13a8.97 8.97 0 0 1 9.01 9.01h3.13A12.1 12.1 0 0 0 2 9.86Zm0-5.86V7.1A14.92 14.92 0 0 1 16.9 22H20A17.9 17.9 0 0 0 2 4Z"
></path>
</svg>
<span class="sr-only">RSS feed</span>
</a>
<button
id="theme-switcher"
class="theme-switcher"
type="button"
aria-label="Switch to dark theme"
aria-pressed="false"
>
<span class="sr-only">Toggle theme</span>
</button>
</div>
</header>
<script is:inline data-theme-script>
(() => {
var key = 'theme';
var legacyKey = 'dark-mode';
var switcher = document.getElementById('theme-switcher');
if (!switcher) return;
function syncSwitcher(theme) {
switcher.setAttribute('aria-pressed', String(theme === 'dark'));
switcher.setAttribute(
'aria-label',
theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'
);
}
var initial = document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light';
syncSwitcher(initial);
var reduced = matchMedia('(prefers-reduced-motion: reduce)');
function apply(theme) {
document.documentElement.dataset.theme = theme;
document.documentElement.style.colorScheme = theme;
syncSwitcher(theme);
}
function runApply(theme) {
if (!reduced.matches && typeof document.startViewTransition === 'function') {
document.startViewTransition(function () {
apply(theme);
});
} else {
apply(theme);
}
}
switcher.addEventListener('click', function () {
var currentTheme =
switcher.getAttribute('aria-pressed') === 'true' ? 'dark' : 'light';
var next = currentTheme === 'dark' ? 'light' : 'dark';
try {
localStorage.setItem(key, next);
localStorage.setItem(legacyKey, JSON.stringify(next === 'dark'));
} catch (e) {}
runApply(next);
});
})();
</script>
<style>
.rss-link {
display: inline-flex;
align-items: center;
justify-content: center;
min-block-size: 44px;
min-inline-size: 44px;
color: inherit;
line-height: 0;
transition: color 150ms ease;
}
.rss-link:hover,
.rss-link:focus-visible {
color: var(--color-link-hover);
}
.rss-icon {
display: block;
}
</style>