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>
77 lines
2.2 KiB
TypeScript
77 lines
2.2 KiB
TypeScript
'use client';
|
|
|
|
import { useTranslations } from 'next-intl';
|
|
import { useState, type FormEvent } from 'react';
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
import { acceptDisclaimer } from './actions';
|
|
|
|
export function DisclaimerForm() {
|
|
const t = useTranslations('Welcome');
|
|
const [adult, setAdult] = useState(false);
|
|
const [aware, setAware] = useState(false);
|
|
const [accepts, setAccepts] = useState(false);
|
|
const [pending, setPending] = useState(false);
|
|
|
|
const allChecked = adult && aware && accepts;
|
|
|
|
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
if (!allChecked) return;
|
|
setPending(true);
|
|
await acceptDisclaimer();
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="space-y-5">
|
|
<Checkbox checked={adult} onChange={setAdult} label={t('checkAdult')} />
|
|
<Checkbox checked={aware} onChange={setAware} label={t('checkAware')} />
|
|
<Checkbox checked={accepts} onChange={setAccepts} label={t('checkAccepts')} />
|
|
<Button type="submit" disabled={!allChecked || pending} size="lg" className="mt-2 w-full">
|
|
{pending ? t('submitting') : t('submit')}
|
|
</Button>
|
|
</form>
|
|
);
|
|
}
|
|
|
|
function Checkbox({
|
|
checked,
|
|
onChange,
|
|
label,
|
|
}: {
|
|
checked: boolean;
|
|
onChange: (next: boolean) => void;
|
|
label: string;
|
|
}) {
|
|
return (
|
|
<label className="group flex cursor-pointer items-start gap-3">
|
|
<span
|
|
className={`relative mt-1 inline-flex h-5 w-5 shrink-0 items-center justify-center border ${
|
|
checked ? 'border-ink bg-ink' : 'border-ink/40 bg-transparent'
|
|
} transition-colors`}
|
|
>
|
|
{checked ? (
|
|
<svg viewBox="0 0 20 20" className="text-carta-light h-3.5 w-3.5">
|
|
<path
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="square"
|
|
strokeLinejoin="miter"
|
|
d="M4 11l4 4 8-9"
|
|
/>
|
|
</svg>
|
|
) : null}
|
|
</span>
|
|
<input
|
|
type="checkbox"
|
|
className="sr-only"
|
|
checked={checked}
|
|
onChange={(e) => onChange(e.target.checked)}
|
|
/>
|
|
<span className="font-display text-ink text-base leading-snug">{label}</span>
|
|
</label>
|
|
);
|
|
}
|