Files
ResumeSite/site/src/components/home/ExperienceTimeline.astro
T
cesnimda 033c9ec315 test: vitest unit + playwright e2e suites; fix a11y, contrast, no-JS reveal
- 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>
2026-07-04 06:14:18 +02:00

99 lines
3.3 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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;
eyebrow?: boolean; // false on the standalone experience page (it has its own H1)
}
const { locale, eyebrow = true } = 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:list={['mx-auto max-w-[--content-max] px-6', eyebrow && 'pt-24']}
data-reveal
>
{
eyebrow && (
<>
<SectionLabel number="03" text={d.home.experienceLabel} />
<h2 class="text-h2 mt-4">{d.home.experienceHeading}</h2>
</>
)
}
<div class="relative mt-10 pl-8">
<span
class="timeline-spine bg-line-strong absolute top-2 bottom-2 left-[5px] w-px origin-top"
aria-hidden="true"></span>
{
items.map((item, i) => (
<>
{i === firstAlongside && (
<p class="text-mono-label text-ink-faint mt-6 mb-3 font-mono font-medium tracking-[0.08em] uppercase">
{d.home.earlierRoles}
</p>
)}
{item.emphasis === 'featured' ? (
<div class="relative pb-10">
<span
class="bg-accent ring-surface-0 absolute top-1.5 -left-8 h-2.5 w-2.5 rounded-full ring-4"
aria-hidden="true"
/>
<h3 class="text-h4 text-ink font-semibold">
{item.role} — {item.employer}
</h3>
<p class="text-mono-meta text-ink-faint mt-1 font-mono">
{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="text-body text-ink-muted mt-3">{item.summary}</p>}
{item.highlights && (
<ul class="mt-3 flex flex-col gap-2">
{item.highlights.map((h) => (
<li class="text-body text-ink-muted flex gap-3">
<span
class="bg-accent mt-2 h-1 w-1 shrink-0 rounded-full"
aria-hidden="true"
/>
<span>{h}</span>
</li>
))}
</ul>
)}
</div>
) : (
<div class="relative py-2.5">
<span
class="bg-ink-faint ring-surface-0 absolute top-4 -left-8 h-2 w-2 rounded-full ring-4"
aria-hidden="true"
/>
<p class="text-small text-ink-muted">
<span class="text-ink-faint font-mono">
{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>