diff --git a/src/components/hero.astro b/src/components/hero.astro
index 9497ad1..6b84e66 100644
--- a/src/components/hero.astro
+++ b/src/components/hero.astro
@@ -16,6 +16,7 @@ const cvFile = locale === 'it' ? 'andrea-lisi-cv-it.pdf' : 'andrea-lisi-cv.pdf';
{hero.greeting}
Andrea Lisi.
{hero.tagline}
+ {hero.eyebrow}
{hero.intro}
diff --git a/src/components/hero.css b/src/components/hero.css
index ee6c9eb..19671cf 100644
--- a/src/components/hero.css
+++ b/src/components/hero.css
@@ -23,6 +23,12 @@
line-height: 1.3;
color: var(--head);
}
+.hero .specialization {
+ margin: 8px 0 0;
+ font-size: 17px;
+ line-height: 1.5;
+ color: var(--body);
+}
.hero .intro {
margin: 22px 0 0;
font-size: 17px;
diff --git a/src/components/work.astro b/src/components/work.astro
index f4bcf76..0ec184c 100644
--- a/src/components/work.astro
+++ b/src/components/work.astro
@@ -61,6 +61,7 @@ const ROOT_TAGS = new Set(['Design']);
)}
{job.years}
+ {job.specialization && {job.specialization}
}
{job.context} · {job.place}
diff --git a/src/components/work.css b/src/components/work.css
index b9d7369..15b29c5 100644
--- a/src/components/work.css
+++ b/src/components/work.css
@@ -77,6 +77,13 @@
}
}
+.work .job-specialization {
+ margin: 7px 0 0;
+ font-size: 16px;
+ line-height: 1.5;
+ color: var(--body);
+}
+
.work .meta {
margin: 5px 0 16px;
font-family: var(--font-mono);
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 85dc836..c6e34d7 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -36,7 +36,7 @@ export const en: Copy = {
hero: {
greeting: 'Ciao. My name is',
tagline: 'Senior Software Engineer.',
- eyebrow: 'Frontend engineering, architecture and developer tools',
+ eyebrow: 'Frontend platforms, developer tooling & design systems',
stats: [
{ label: 'Based in', value: 'Vienna, Austria' },
{ label: 'Working', value: 'Remote or hybrid', tone: 'now' },
@@ -163,8 +163,9 @@ export const en: Copy = {
place: 'VIENNA, AT',
title: 'Senior Software Engineer',
company: 'Tricentis',
+ specialization: 'Frontend Platform, Governance & Design Systems',
context: 'Work on Tosca Cloud',
- tags: ['Micro-frontends', '16 projects', '12+ teams', 'Quality gates'],
+ tags: ['React', 'TypeScript', 'Micro-frontends', '16 projects', '12+ teams', 'Quality gates'],
current: true,
paragraphs: [
'Cross-team decisions. On Tosca Cloud, together with the team’s architect, I was a point of contact for initiatives spanning the monorepo. I worked with developers, engineering managers, principal engineers from other platforms and product stakeholders to define shared solutions.',
diff --git a/src/i18n/it.ts b/src/i18n/it.ts
index f05d74c..30394c7 100644
--- a/src/i18n/it.ts
+++ b/src/i18n/it.ts
@@ -37,7 +37,7 @@ export const it: Copy = {
hero: {
greeting: 'Ciao. Mi chiamo',
tagline: 'Senior Software Engineer.',
- eyebrow: 'Sviluppo frontend, architettura e strumenti per sviluppatori',
+ eyebrow: 'Piattaforme frontend, strumenti per sviluppatori e design system',
stats: [
{ label: 'Dove vivo', value: 'Vienna, Austria' },
{ label: 'Come lavoro', value: 'Remoto o ibrido', tone: 'now' },
@@ -171,8 +171,9 @@ export const it: Copy = {
place: 'VIENNA, AT',
title: 'Senior Software Engineer',
company: 'Tricentis',
+ specialization: 'Piattaforma frontend, governance e design system',
context: 'Esperienza su Tosca Cloud',
- tags: ['Microfrontend', '16 progetti', '12+ team', 'Quality gates'],
+ tags: ['React', 'TypeScript', 'Microfrontend', '16 progetti', '12+ team', 'Quality gates'],
current: true,
paragraphs: [
'Decisioni tra team. Su Tosca Cloud, insieme all’architetto del team, sono stato un riferimento per le iniziative dell’intera monorepo. Ho lavorato con sviluppatori, engineering manager, principal engineer di altre piattaforme e referenti di prodotto per definire le soluzioni condivise.',
diff --git a/src/i18n/types.ts b/src/i18n/types.ts
index 5e31601..1132602 100644
--- a/src/i18n/types.ts
+++ b/src/i18n/types.ts
@@ -60,6 +60,8 @@ export type WorkEntry = {
years: string;
place: string;
company: string;
+ /** Scope of the experience, separate from the official job title. */
+ specialization?: string;
/**
* What the place was, on the line under the title. Never the job title again:
* three of these repeated it word for word, which the phone layout made
diff --git a/tests/e2e/homepage.spec.ts b/tests/e2e/homepage.spec.ts
index bcdc529..62be37f 100644
--- a/tests/e2e/homepage.spec.ts
+++ b/tests/e2e/homepage.spec.ts
@@ -58,13 +58,15 @@ for (const locale of ['en', 'it'] as const) {
expect((description as string).length).toBeLessThan(155);
});
- test('opens with the greeting, the name and the tagline', async ({ page }) => {
+ test('opens with the greeting, the name and the tagline', async ({ page }, testInfo) => {
await page.goto(home);
await expect(page.getByText(copy.hero.greeting)).toBeVisible();
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Andrea Lisi.');
await expect(page.getByText(copy.hero.tagline)).toBeVisible();
+ await expect(page.locator('.hero .specialization')).toHaveText(copy.hero.eyebrow);
await expect(page.getByText(copy.hero.intro)).toBeVisible();
+ await page.locator('.hero').screenshot({ path: testInfo.outputPath('hero-desktop.png') });
});
test('shows the portrait, served as WebP with a JPEG fallback', async ({ page }) => {
@@ -226,6 +228,19 @@ for (const locale of ['en', 'it'] as const) {
await expect(page.locator('.work .job-title').first()).toContainText(
'Senior Software Engineer',
);
+ await expect(page.locator('.work .job-specialization')).toHaveCount(1);
+ await expect(page.locator('.work .job-specialization')).toHaveText(
+ copy.work[0]?.specialization ?? '',
+ );
+ await expect(page.locator('.work .job').first().locator('.meta')).toContainText(
+ 'Tosca Cloud',
+ );
+ for (const tag of ['React', 'TypeScript']) {
+ await expect(
+ page.locator('.work .job').first().locator('.chip').filter({ hasText: tag }),
+ ).toBeVisible();
+ await expect(page.locator('.work .job-title').first()).not.toContainText(tag);
+ }
await expect(page.locator('#examples')).toHaveCount(0);
await expect(
page
@@ -298,7 +313,7 @@ for (const locale of ['en', 'it'] as const) {
await context.close();
});
- test('holds together at phone width', async ({ page }) => {
+ test('holds together at phone width', async ({ page }, testInfo) => {
await page.setViewportSize({ width: 375, height: 812 });
await page.goto(home);
@@ -310,6 +325,11 @@ for (const locale of ['en', 'it'] as const) {
const cv = await page.locator('.hero .cv').boundingBox();
expect(cv).not.toBeNull();
expect((cv?.y ?? 1000) + (cv?.height ?? 0)).toBeLessThanOrEqual(812);
+ await page.locator('.hero').screenshot({ path: testInfo.outputPath('hero-mobile.png') });
+ await page
+ .locator('.work .job')
+ .first()
+ .screenshot({ path: testInfo.outputPath('tricentis-mobile.png') });
});
test('passes axe accessibility checks (no critical/serious violations)', async ({ page }) => {