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
@@ -3,8 +3,8 @@ import { redirect } from 'next/navigation';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { setRequestLocale } from 'next-intl/server';
|
||||
|
||||
import { Masthead } from '@/components/masthead';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { getServerSession } from '@/lib/auth';
|
||||
|
||||
import { fetchCurrentPlan, regeneratePlan } from './actions';
|
||||
@@ -27,27 +27,58 @@ export default async function PlanPage({ params: { locale } }: { params: { local
|
||||
return <PlanPageContent plan={plan} />;
|
||||
}
|
||||
|
||||
const ITALIAN_DATE = new Intl.DateTimeFormat('it-IT', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
});
|
||||
|
||||
function formatWeek(weekStart: string): string {
|
||||
const start = new Date(`${weekStart}T00:00:00`);
|
||||
const end = new Date(start);
|
||||
end.setDate(end.getDate() + 6);
|
||||
const startStr = new Intl.DateTimeFormat('it-IT', { day: 'numeric', month: 'long' }).format(
|
||||
start,
|
||||
);
|
||||
const endStr = ITALIAN_DATE.format(end);
|
||||
return `${startStr} – ${endStr}`;
|
||||
}
|
||||
|
||||
function PlanPageContent({ plan }: { plan: Awaited<ReturnType<typeof fetchCurrentPlan>> }) {
|
||||
const t = useTranslations('Plan');
|
||||
|
||||
return (
|
||||
<main className="mx-auto min-h-screen max-w-5xl px-4 py-10 sm:px-6">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-start justify-between gap-4">
|
||||
<div>
|
||||
<CardTitle>{t('title')}</CardTitle>
|
||||
<CardDescription>
|
||||
{plan ? t('weekOf', { date: plan.weekStart }) : t('noPlan')}
|
||||
</CardDescription>
|
||||
<div className="mx-auto min-h-screen max-w-6xl px-6 sm:px-10">
|
||||
<Masthead issueLabel="N. 05 — Cucina" />
|
||||
<main className="py-12 sm:py-16">
|
||||
<div className="grid items-end gap-6 md:grid-cols-12">
|
||||
<div className="md:col-span-8">
|
||||
<p className="editorial-eyebrow animate-fade-up">Capitolo V</p>
|
||||
<h1 className="font-display text-display text-ink animate-fade-up mt-3 font-medium leading-[0.95] tracking-tight [animation-delay:120ms]">
|
||||
{t('title')}
|
||||
<span className="text-pomodoro">.</span>
|
||||
</h1>
|
||||
<p className="font-display text-ink-soft animate-fade-up mt-5 max-w-2xl text-xl italic leading-snug [animation-delay:240ms]">
|
||||
{plan ? formatWeek(plan.weekStart) : t('noPlan')}
|
||||
</p>
|
||||
</div>
|
||||
<form action={regeneratePlan}>
|
||||
<Button type="submit" variant={plan ? 'outline' : 'default'} size="sm">
|
||||
{plan ? t('regenerate') : t('generate')}
|
||||
</Button>
|
||||
</form>
|
||||
</CardHeader>
|
||||
<CardContent>{plan ? <PlanWeek plan={plan} /> : null}</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
<div className="animate-fade-up [animation-delay:360ms] md:col-span-4 md:text-right">
|
||||
<form action={regeneratePlan}>
|
||||
<Button type="submit" variant={plan ? 'outline' : 'default'} size="lg">
|
||||
{plan ? t('regenerate') : t('generate')}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rule animate-rule-in my-10 [animation-delay:420ms]" />
|
||||
|
||||
{plan ? (
|
||||
<div className="animate-fade-up [animation-delay:480ms]">
|
||||
<PlanWeek plan={plan} />
|
||||
</div>
|
||||
) : null}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user