033c9ec315
- vitest: dictionary parity, slug-map bijection, content-schema validation, JSON-LD (23) - playwright: both locales, language-switch mapping (incl. no-JS), CV downloads, form happy-path + honeypot, axe a11y on 5 templates x 2 themes (23) - fix: raise ink-faint + light accent to meet WCAG AA 4.5:1 (axe-verified) - fix: gate reveal animations behind html.js so content is visible without JS (progressive enhancement - was hidden at opacity 0); scan reduced-motion path - fix: validate contact form before the anti-bot time-trap (no false success) - chore: ESLint node globals, typed diagram props, resvg fontFiles, prettier pass Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
92 lines
2.9 KiB
Plaintext
92 lines
2.9 KiB
Plaintext
---
|
|
import { useDict, interpolate } from '@i18n/t';
|
|
import type { Locale } from '@i18n/locales';
|
|
import { pathFor, type PageId } from '@i18n/slugMap';
|
|
import { getProfile } from '@lib/content';
|
|
import LangSwitch from '@components/ui/LangSwitch.astro';
|
|
|
|
interface Props {
|
|
locale: Locale;
|
|
pageId: PageId;
|
|
}
|
|
const { locale, pageId } = Astro.props;
|
|
const d = useDict(locale);
|
|
const p = getProfile(locale);
|
|
const year = new Date().getFullYear();
|
|
|
|
const nav: { id: PageId; label: string }[] = [
|
|
{ id: 'projects', label: d.nav.projects },
|
|
{ id: 'experience', label: d.nav.experience },
|
|
{ id: 'about', label: d.nav.about },
|
|
{ id: 'contact', label: d.nav.contact },
|
|
];
|
|
---
|
|
|
|
<footer class="border-line bg-surface-1 mt-24 border-t">
|
|
<div class="mx-auto grid max-w-[--content-max] gap-10 px-6 py-14 sm:grid-cols-2 lg:grid-cols-4">
|
|
<div class="lg:col-span-2">
|
|
<p class="font-display text-h4 text-ink font-semibold">Connor Babbington</p>
|
|
<p class="text-small text-ink-muted mt-2 max-w-sm">{d.footer.tagline}</p>
|
|
<p class="text-small text-ink-muted mt-1 max-w-sm">{d.footer.availability}</p>
|
|
</div>
|
|
|
|
<nav aria-label={d.a11y.primaryNav}>
|
|
<p class="mono-label mb-3">{d.nav.projects}</p>
|
|
<ul class="text-small text-ink-muted flex flex-col gap-2">
|
|
{
|
|
nav.map((item) => (
|
|
<li>
|
|
<a class="hover:text-ink transition-colors" href={pathFor(item.id, locale)}>
|
|
{item.label}
|
|
</a>
|
|
</li>
|
|
))
|
|
}
|
|
</ul>
|
|
</nav>
|
|
|
|
<div>
|
|
<p class="mono-label mb-3">Contact</p>
|
|
<ul class="text-mono-meta text-ink-muted flex flex-col gap-2 font-mono">
|
|
<li>
|
|
<a class="hover:text-ink transition-colors" href={`mailto:${p.links.email}`}
|
|
>{p.links.email}</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a class="hover:text-ink transition-colors" href={p.links.linkedin} rel="me noopener"
|
|
>LinkedIn ↗</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a class="hover:text-ink transition-colors" href={p.links.gitea} rel="me noopener"
|
|
>git.cesnimda.uk ↗</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a class="hover:text-ink transition-colors" href={pathFor('cv', locale)}
|
|
>{d.cv.download}</a
|
|
>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
class="border-line mx-auto flex max-w-[--content-max] flex-col items-start justify-between gap-4 border-t px-6 py-6 sm:flex-row sm:items-center"
|
|
>
|
|
<p class="text-mono-meta text-ink-faint font-mono">
|
|
{interpolate(d.footer.copyright, { year })}
|
|
</p>
|
|
<div class="flex items-center gap-4">
|
|
<a
|
|
href={pathFor('colophon', locale)}
|
|
class="text-mono-meta text-ink-muted hover:text-ink font-mono transition-colors"
|
|
>
|
|
{d.footer.colophon}
|
|
</a>
|
|
<LangSwitch locale={locale} pageId={pageId} />
|
|
</div>
|
|
</div>
|
|
</footer>
|