1fce1b3cec
- UI atoms: Chip, Button, SplitCvButton, LangSwitch, ThemeToggle, Card, SectionLabel, TraceMotif, FramedImage - core: Base/Seo/Header/MobileNav/Footer/HintBar/SkipLink/ThemeScript - homepage sections: Hero, ProofStrip, SkillsGrid, ProjectCards, ExperienceTimeline, AboutTeaser, ContactBand + Homepage composition (EN/NO) - behaviour modules: theme, nav (overlay/hint/copy/header), observer (reveal/spy), lightbox, form — progressive enhancement, fail-silent - SEO head component: meta, hreflang from slug map, JSON-LD, OG references - move content data to src/data (avoid Astro reserved content-collection folder) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
29 lines
891 B
TypeScript
29 lines
891 B
TypeScript
/*
|
|
Theme toggle (A6). The no-flash init in ThemeScript already set data-theme before
|
|
paint; this only wires the button, persists the choice, and keeps the aria-label
|
|
describing the *next* action. Fail-silent if the button is absent.
|
|
*/
|
|
const html = document.documentElement;
|
|
const btn = document.getElementById('theme-toggle');
|
|
|
|
function labelFor(theme: string): string {
|
|
if (!btn) return '';
|
|
return theme === 'dark'
|
|
? (btn.dataset.toLight ?? '')
|
|
: (btn.dataset.toDark ?? '');
|
|
}
|
|
|
|
if (btn) {
|
|
btn.setAttribute('aria-label', labelFor(html.dataset.theme ?? 'dark'));
|
|
btn.addEventListener('click', () => {
|
|
const next = html.dataset.theme === 'light' ? 'dark' : 'light';
|
|
html.dataset.theme = next;
|
|
try {
|
|
localStorage.setItem('theme', next);
|
|
} catch {
|
|
/* private mode — ignore */
|
|
}
|
|
btn.setAttribute('aria-label', labelFor(next));
|
|
});
|
|
}
|