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,63 @@
|
||||
---
|
||||
/*
|
||||
CV split button (MICRO_INTERACTIONS): primary zone downloads the current-locale CV;
|
||||
the chevron is a native <details> disclosure listing both languages with file sizes
|
||||
— works with no JavaScript. nav.ts enhances it with outside-click / Esc close.
|
||||
*/
|
||||
import { useDict, interpolate } from '@i18n/t';
|
||||
import type { Locale } from '@i18n/locales';
|
||||
import { getProfile } from '@lib/content';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
compact?: boolean;
|
||||
}
|
||||
const { locale, compact = false } = Astro.props;
|
||||
const d = useDict(locale);
|
||||
const p = getProfile(locale);
|
||||
const current = p.cv[locale];
|
||||
const other = locale === 'en' ? p.cv.no : p.cv.en;
|
||||
const otherLabel = locale === 'en' ? d.cv.norsk : d.cv.english;
|
||||
const currentLabel = locale === 'en' ? d.cv.english : d.cv.norsk;
|
||||
---
|
||||
|
||||
<div class="cv-split relative inline-flex items-stretch">
|
||||
<a
|
||||
href={current.path}
|
||||
download
|
||||
class="inline-flex items-center rounded-l-sm bg-accent px-4 py-2 text-small font-semibold text-accent-ink transition-[filter] duration-[--dur-quick] hover:brightness-105"
|
||||
>
|
||||
{d.cv.download}
|
||||
{!compact && <span class="ml-2 font-mono text-mono-label opacity-70">EN/NO</span>}
|
||||
</a>
|
||||
<details class="cv-menu">
|
||||
<summary
|
||||
class="flex cursor-pointer list-none items-center rounded-r-sm border-l border-accent-ink/20 bg-accent px-2 text-accent-ink transition-[filter] duration-[--dur-quick] hover:brightness-105"
|
||||
aria-label={d.cv.download}
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
|
||||
</svg>
|
||||
</summary>
|
||||
<div
|
||||
class="absolute right-0 top-[calc(100%+8px)] z-30 w-64 overflow-hidden rounded-md border border-line-strong bg-surface-2 shadow-[var(--shadow-overlay)]"
|
||||
>
|
||||
<a
|
||||
href={current.path}
|
||||
download
|
||||
class="flex items-center justify-between gap-2 px-4 py-3 text-small text-ink hover:bg-surface-1"
|
||||
>
|
||||
<span><span class="mr-2 font-mono text-mono-label text-accent">{d.lang[locale]}</span>{currentLabel}</span>
|
||||
<span class="mono-meta">{interpolate(d.cv.fileMeta, { size: current.sizeKb })}</span>
|
||||
</a>
|
||||
<a
|
||||
href={other.path}
|
||||
download
|
||||
class="flex items-center justify-between gap-2 border-t border-line px-4 py-3 text-small text-ink-muted hover:bg-surface-1 hover:text-ink"
|
||||
>
|
||||
<span><span class="mr-2 font-mono text-mono-label">{locale === 'en' ? 'NO' : 'EN'}</span>{otherLabel}</span>
|
||||
<span class="mono-meta">{interpolate(d.cv.fileMeta, { size: other.sizeKb })}</span>
|
||||
</a>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
Reference in New Issue
Block a user