feat(web): editorial slow-food redesign across the whole UI
A complete visual + tonal pass on every page. Replaces the previous shadcn
slate+emerald defaults with an Italian editorial aesthetic — refined slow-food
magazine crossed with a luxury cookbook. Mobile-first, premium, distinctly
Italian, no generic AI-wellness vibes.
Tokens:
- New palette: carta (cream parchment), ink (warm black), oliva (deep olive),
pomodoro (terracotta), oro (aged gold), rule (hairline). All exposed as
HSL CSS variables and as Tailwind colors. Dark mode = oxblood/cream.
- Type stack: Fraunces (variable, optical-sizing auto, italic + roman) for
display / Inter Tight for body / JetBrains Mono with tnum + zero for
numerals. Replaces Inter-only setup.
- Font feature settings on body and .font-display for ligatures, opsz
and stylistic alternates.
- Subtle SVG paper grain on body::before (multiply blend, 4.5% opacity).
- 2px radius default (editorial print, not soft web).
Component primitives:
- Button — mono uppercase 11px tracking-widest, sharp corners, hover
inverts ink/cream. Variants tuned for the new palette.
- Input — bottom-only hairline, Fraunces 18px text, italic placeholders.
- Label — tiny mono caps, ink-soft.
- Card — top + bottom hairlines only, no shadow; Fraunces title and italic
description; CardFooter has its own top-rule.
- Select — same hairline-bottom trigger as Input; SelectContent panel uses
a hard 6px offset shadow for an editorial print stamp feel.
New component:
- Masthead — top-of-page newspaper header with thick top-rule, KP wordmark
with terracotta dot, "ANNO I · N. 0X" issue label, today's date in Italian.
Pages:
- / — Massive Fraunces "Una keto italiana, sostenibile." with terracotta
dot, italic Fraunces tagline, right-column nav with category eyebrows
(CUCINA / PROFILO) and arrow rule. Bottom: "Tre principi" with roman
numerals I/II/III. Disclaimer in italic.
- /sign-in — "CAPITOLO II / Bentornato in cucina." split layout.
- /sign-up — "CAPITOLO III / Inizia il tuo percorso." Italian copy refresh.
- /welcome — Editor's-letter layout with the four PRD §14.3 points as a
Roman-numeral list; custom hand-drawn check icon for the consent boxes.
- /profile — 7/5 split: form on the left (anagrafica + peso group), summary
on the right with massive mono numerals (BMR, TDEE, multiplier) and italic
"kcal" units; empty-state copy until the profile is saved.
- /plan — "CAPITOLO V / Il tuo piano." Italian-formatted week range. Each
day has a Roman-numeral mark (I-VII), Fraunces day name, mono kcal total.
Each meal slot uses Roman numerals (I-IV) for the meal index.
- Cookie banner — refreshed to match (italic Fraunces, "PRIVACY" eyebrow,
outline button).
Italian copy refresh:
- Headline: "Una keto italiana, sostenibile" (was "KetoPath")
- Tagline: narrative arc instead of marketing line
- CTAs: "Iscriviti" / "Accedi" (was "Inizia gratis" / "Scopri come funziona")
- Greeting: "Buongiorno, {name}." (was "Accesso effettuato come ...")
- Profile: weight columns become Iniziale / Attuale / Obiettivo under a
"Peso (chilogrammi)" group eyebrow.
E2E updated to match: home looks for the new headline and CTA labels;
profile uses the new "Come ti chiami" / "Iniziale / Attuale / Obiettivo"
labels and asserts numeric values separately from the "kcal" unit span.
All 4 specs pass. Verified visually at 1280×900 and 375×812.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f8eaf2f493
commit
59a311cfc9
26 files changed
+933
-440
No files matched your search
@@ -14,17 +14,17 @@ test.describe('email/password authentication', () => {
|
||||
|
||||
// Sign up
|
||||
await page.goto('/sign-up');
|
||||
await page.getByLabel('Nome').fill(name);
|
||||
await page.getByLabel('Come ti chiami').fill(name);
|
||||
await page.getByLabel('Email').fill(email);
|
||||
await page.getByLabel('Password').fill(password);
|
||||
await page.getByRole('button', { name: 'Crea account' }).click();
|
||||
|
||||
await page.waitForURL('/');
|
||||
await expect(page.getByText(`Accesso effettuato come ${name}`)).toBeVisible();
|
||||
await expect(page.getByText(`Buongiorno, ${name}`)).toBeVisible();
|
||||
|
||||
// Sign out
|
||||
await page.getByRole('button', { name: 'Esci' }).click();
|
||||
await expect(page.getByRole('link', { name: 'Inizia gratis' })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Iscriviti' })).toBeVisible();
|
||||
|
||||
// Sign in with the same credentials
|
||||
await page.goto('/sign-in');
|
||||
@@ -33,7 +33,7 @@ test.describe('email/password authentication', () => {
|
||||
await page.getByRole('button', { name: 'Accedi', exact: true }).click();
|
||||
|
||||
await page.waitForURL('/');
|
||||
await expect(page.getByText(`Accesso effettuato come ${name}`)).toBeVisible();
|
||||
await expect(page.getByText(`Buongiorno, ${name}`)).toBeVisible();
|
||||
});
|
||||
|
||||
test('shows an error message on invalid credentials', async ({ page }) => {
|
||||
|
||||
@@ -4,8 +4,8 @@ test('home page renders Italian copy and CTAs', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page).toHaveTitle(/KetoPath/);
|
||||
await expect(page.getByRole('heading', { name: 'KetoPath' })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Inizia gratis' })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Scopri come funziona' })).toBeVisible();
|
||||
await expect(page.getByText(/non sostituiscono il parere di un medico/)).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: /Una keto italiana, sostenibile/ })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Iscriviti' })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Accedi' })).toBeVisible();
|
||||
await expect(page.getByText(/non sostituiscono il parere/)).toBeVisible();
|
||||
});
|
||||
@@ -13,20 +13,20 @@ test('profile flow — submit returns BMR/TDEE for the Michele PRD persona', asy
|
||||
|
||||
// Sign-up gives us an authenticated session
|
||||
await page.goto('/sign-up');
|
||||
await page.getByLabel('Nome').fill(name);
|
||||
await page.getByLabel('Come ti chiami').fill(name);
|
||||
await page.getByLabel('Email').fill(email);
|
||||
await page.getByLabel('Password').fill(password);
|
||||
await page.getByRole('button', { name: 'Crea account' }).click();
|
||||
await page.waitForURL('/');
|
||||
|
||||
// Open profile from home — first time we'll be redirected through /welcome
|
||||
await page.getByRole('link', { name: 'Completa il tuo profilo' }).click();
|
||||
await page.getByRole('link', { name: 'Profilo & calcoli' }).click();
|
||||
await page.waitForURL('/welcome');
|
||||
|
||||
// Accept the medical disclaimer (3 mandatory checkboxes + submit)
|
||||
await page.getByLabel(/Confermo di essere maggiorenne/).check();
|
||||
await page.getByLabel(/Dichiaro di non avere condizioni mediche escludenti/).check();
|
||||
await page.getByLabel(/Accetto che KetoPath non sostituisce/).check();
|
||||
await page.getByText(/Confermo di essere maggiorenne/).click();
|
||||
await page.getByText(/Dichiaro di non avere condizioni mediche escludenti/).click();
|
||||
await page.getByText(/Accetto che KetoPath non sostituisce/).click();
|
||||
await page.getByRole('button', { name: 'Accetto e proseguo' }).click();
|
||||
await page.waitForURL('/profile');
|
||||
|
||||
@@ -35,15 +35,17 @@ test('profile flow — submit returns BMR/TDEE for the Michele PRD persona', asy
|
||||
await page.getByLabel('Sesso').click();
|
||||
await page.getByRole('option', { name: 'Maschio' }).click();
|
||||
await page.getByLabel('Altezza (cm)').fill('170');
|
||||
await page.getByLabel('Peso iniziale (kg)').fill('76');
|
||||
await page.getByLabel('Peso attuale (kg)').fill('76');
|
||||
await page.getByLabel('Peso obiettivo (kg)').fill('70');
|
||||
await page.getByLabel('Iniziale').fill('76');
|
||||
await page.getByLabel('Attuale').fill('76');
|
||||
await page.getByLabel('Obiettivo').fill('70');
|
||||
await page.getByLabel('Livello di attività').click();
|
||||
await page.getByRole('option', { name: /Sedentario/ }).click();
|
||||
await page.getByRole('button', { name: 'Salva profilo' }).click();
|
||||
|
||||
// BMR/TDEE summary panel becomes visible after a successful save
|
||||
await expect(page.getByText('Calcolo personalizzato')).toBeVisible();
|
||||
await expect(page.getByText('1583 kcal')).toBeVisible(); // BMR Michele
|
||||
await expect(page.getByText('1899 kcal')).toBeVisible(); // TDEE = 1582.5 × 1.2
|
||||
// Numbers are rendered in monospace next to a separate "kcal" unit span,
|
||||
// so check for the bare numeric values.
|
||||
await expect(page.getByText('1583', { exact: true })).toBeVisible();
|
||||
await expect(page.getByText('1899', { exact: true })).toBeVisible();
|
||||
});
|
||||
Reference in new issue
Block a user