feat: core layout, UI atoms, homepage + behaviour modules
- 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>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
---
|
||||
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="mt-24 border-t border-line bg-surface-1">
|
||||
<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 font-semibold text-ink">Connor Babbington</p>
|
||||
<p class="mt-2 max-w-sm text-small text-ink-muted">{d.footer.tagline}</p>
|
||||
<p class="mt-1 max-w-sm text-small text-ink-muted">{d.footer.availability}</p>
|
||||
</div>
|
||||
|
||||
<nav aria-label={d.a11y.primaryNav}>
|
||||
<p class="mono-label mb-3">{d.nav.projects}</p>
|
||||
<ul class="flex flex-col gap-2 text-small text-ink-muted">
|
||||
{
|
||||
nav.map((item) => (
|
||||
<li>
|
||||
<a class="transition-colors hover:text-ink" href={pathFor(item.id, locale)}>
|
||||
{item.label}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div>
|
||||
<p class="mono-label mb-3">Contact</p>
|
||||
<ul class="flex flex-col gap-2 font-mono text-mono-meta text-ink-muted">
|
||||
<li>
|
||||
<a class="transition-colors hover:text-ink" href={`mailto:${p.links.email}`}
|
||||
>{p.links.email}</a
|
||||
>
|
||||
</li>
|
||||
<li><a class="transition-colors hover:text-ink" href={p.links.linkedin} rel="me noopener">LinkedIn ↗</a></li>
|
||||
<li>
|
||||
<a class="transition-colors hover:text-ink" href={p.links.gitea} rel="me noopener"
|
||||
>git.cesnimda.uk ↗</a
|
||||
>
|
||||
</li>
|
||||
<li><a class="transition-colors hover:text-ink" href={pathFor('cv', locale)}>{d.cv.download}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mx-auto flex max-w-[--content-max] flex-col items-start justify-between gap-4 border-t border-line px-6 py-6 sm:flex-row sm:items-center"
|
||||
>
|
||||
<p class="font-mono text-mono-meta text-ink-faint">
|
||||
{interpolate(d.footer.copyright, { year })}
|
||||
</p>
|
||||
<div class="flex items-center gap-4">
|
||||
<a
|
||||
href={pathFor('colophon', locale)}
|
||||
class="font-mono text-mono-meta text-ink-muted transition-colors hover:text-ink"
|
||||
>
|
||||
{d.footer.colophon}
|
||||
</a>
|
||||
<LangSwitch locale={locale} pageId={pageId} />
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
Reference in New Issue
Block a user