feat: add design system, theme toggle, SEO head and base layout

- Huemint Magazine tokens for light/dark themes with AA-safe accent variants
- FOUC-free theme init + toggle (localStorage, follows OS until chosen)
- Seo component (canonical, OG, Twitter, WebSite/Person JSON-LD)
- Header with popover mobile menu, 4-column footer with social links
- Container, GridLines, SectionHeading, Button, Badge, Card, ExternalLink
- site.json / social.json validated with zod
- dev-only /styleguide preview

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 00:47:50 +03:00
parent c5e0b009ca
commit d8a79ccbe3
19 changed files with 915 additions and 36 deletions
+94
View File
@@ -0,0 +1,94 @@
---
import { Icon } from 'astro-icon/components';
import { site, social } from '../lib/data';
import Badge from './Badge.astro';
import Container from './Container.astro';
import ExternalLink from './ExternalLink.astro';
const year = new Date().getFullYear();
const linkClass =
'decoration-accent underline-offset-4 hover:underline decoration-2 inline-flex items-center gap-2';
const titleClass = 'font-display mb-4 text-sm font-bold tracking-wide uppercase';
---
<footer class="surface-alt border-line border-t">
<Container class="grid gap-10 py-14 sm:grid-cols-2 lg:grid-cols-[1.5fr_1fr_1fr_1fr]">
<section aria-labelledby="footer-about">
<h2 id="footer-about" class="font-display text-3xl font-black tracking-tight uppercase">
{site.name}
</h2>
<p class="mt-3 max-w-sm">{site.footer.about}</p>
<Badge variant="solid" icon="tabler:antenna" class="mt-4">
{site.author.callsign} · {site.author.locator}
</Badge>
</section>
<nav aria-labelledby="footer-links">
<h2 id="footer-links" class={titleClass}>
{site.footer.quickLinksTitle}
</h2>
<ul class="flex flex-col gap-2">
{site.nav.map((item) => (
<li>
<a href={item.href} class={linkClass}>
{item.label}
</a>
</li>
))}
<li>
<a href={site.footer.rss.href} class={linkClass}>
<Icon name="tabler:rss" class="size-4" aria-hidden="true" />
{site.footer.rss.label}
</a>
</li>
</ul>
</nav>
<nav aria-labelledby="footer-legal">
<h2 id="footer-legal" class={titleClass}>
{site.footer.legalTitle}
</h2>
<ul class="flex flex-col gap-2">
{site.footer.legal.map((item) => (
<li>
<a href={item.href} class={linkClass}>
{item.label}
</a>
</li>
))}
</ul>
</nav>
{social.length > 0 && (
<section aria-labelledby="footer-social">
<h2 id="footer-social" class={titleClass}>
{site.footer.socialTitle}
</h2>
<ul class="flex flex-wrap gap-2">
{social.map((s) => (
<li>
<ExternalLink
href={s.href}
icon={false}
title={s.label}
class="border-fg hover:bg-fg hover:text-bg size-11 justify-center border transition-colors"
>
<Icon name={s.icon} class="size-5" aria-hidden="true" />
<span class="sr-only">{s.label}</span>
</ExternalLink>
</li>
))}
</ul>
</section>
)}
</Container>
<div class="border-line border-t">
<Container class="flex flex-col gap-2 py-5 text-sm sm:flex-row sm:items-center sm:justify-between">
<p>
© {year} {site.author.name} · {site.footer.license}
</p>
<p class="font-display font-bold tracking-wide uppercase">{site.footer.signoff}</p>
</Container>
</div>
</footer>