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,77 @@
|
||||
---
|
||||
import type { Locale } from '@i18n/locales';
|
||||
import { useDict } from '@i18n/t';
|
||||
import { homeAnchor } from '@i18n/slugMap';
|
||||
import { getExperience } from '@lib/content';
|
||||
import SectionLabel from '@components/ui/SectionLabel.astro';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
compact?: boolean; // when true (experience page), render heading differently
|
||||
}
|
||||
const { locale } = Astro.props;
|
||||
const d = useDict(locale);
|
||||
const items = getExperience(locale);
|
||||
const firstAlongside = items.findIndex((i) => i.alongside);
|
||||
|
||||
const period = (from: number, to: number | null) => `${from}–${to ?? d.home.present}`;
|
||||
---
|
||||
|
||||
<section id={homeAnchor('experience', locale)} class="mx-auto max-w-[--content-max] px-6 pt-24" data-reveal>
|
||||
<SectionLabel number="03" text={d.home.experienceLabel} />
|
||||
<h2 class="mt-4 text-h2">{d.home.experienceHeading}</h2>
|
||||
|
||||
<div class="relative mt-10 pl-8">
|
||||
<span
|
||||
class="timeline-spine absolute bottom-2 left-[5px] top-2 w-px origin-top bg-line-strong"
|
||||
aria-hidden="true"></span>
|
||||
|
||||
{
|
||||
items.map((item, i) => (
|
||||
<>
|
||||
{i === firstAlongside && (
|
||||
<p class="mb-3 mt-6 font-mono text-mono-label font-medium uppercase tracking-[0.08em] text-ink-faint">
|
||||
{d.home.earlierRoles}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{item.emphasis === 'featured' ? (
|
||||
<div class="relative pb-10">
|
||||
<span class="absolute -left-8 top-1.5 h-2.5 w-2.5 rounded-full bg-accent ring-4 ring-surface-0" aria-hidden="true" />
|
||||
<h3 class="text-h4 font-semibold text-ink">
|
||||
{item.role} — {item.employer}
|
||||
</h3>
|
||||
<p class="mt-1 font-mono text-mono-meta text-ink-faint">
|
||||
{period(item.period.from, item.period.to)} · {item.location}
|
||||
{item.progression &&
|
||||
` · ${item.progression.map((p) => `${p.from}–${p.to} ${p.label}`).join(' → ')}`}
|
||||
</p>
|
||||
{item.summary && <p class="mt-3 text-body text-ink-muted">{item.summary}</p>}
|
||||
{item.highlights && (
|
||||
<ul class="mt-3 flex flex-col gap-2">
|
||||
{item.highlights.map((h) => (
|
||||
<li class="flex gap-3 text-body text-ink-muted">
|
||||
<span class="mt-2 h-1 w-1 shrink-0 rounded-full bg-accent" aria-hidden="true" />
|
||||
<span>{h}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div class="relative py-2.5">
|
||||
<span class="absolute -left-8 top-4 h-2 w-2 rounded-full bg-ink-faint ring-4 ring-surface-0" aria-hidden="true" />
|
||||
<p class="text-small text-ink-muted">
|
||||
<span class="font-mono text-ink-faint">{period(item.period.from, item.period.to)}</span>
|
||||
<span class="mx-2">·</span>
|
||||
<span class="text-ink">{item.role}</span>
|
||||
<span class="mx-2">·</span>
|
||||
{item.employer}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
Reference in New Issue
Block a user