Redesign: Editorial Fresh design system

Complete visual overhaul with distinctive editorial aesthetic:

Design System:
- New fonts: Fraunces (display) + DM Sans (body)
- Color palette: Cream background, ink text, coral accents
- Custom CSS variables and utility classes
- Sharp edges (no rounded corners) for editorial feel

Components:
- Updated Button with new variants and colors
- Updated Input with editorial styling
- New card-editorial class with accent bar

Pages:
- Homepage: Full redesign with hero, features, CTA
- Auth layout: Split screen with branding side
- Login/Register: Editorial styling with tags
- Dashboard: Cards with accent bars
- Subscription: Clean feature comparison table

Typography:
- Fraunces for all headings (serif, characterful)
- DM Sans for body text (clean, readable)
- Editorial tags (uppercase, accent color)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Michele
2026-02-01 16:27:23 +01:00
parent 09d1c39ec0
commit afdec23a84
16 changed files with 785 additions and 285 deletions

View File

@@ -1,12 +1,61 @@
import Link from 'next/link'
export default function AuthLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
{children}
<div className="min-h-screen bg-cream flex">
{/* Left side - Branding */}
<div className="hidden lg:flex lg:w-1/2 bg-ink p-12 flex-col justify-between relative overflow-hidden">
{/* Decorative elements */}
<div className="absolute top-0 right-0 w-64 h-64 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2"></div>
<div className="absolute bottom-0 left-0 w-96 h-96 bg-accent/5 rounded-full translate-y-1/2 -translate-x-1/2"></div>
{/* Logo */}
<Link href="/" className="relative z-10">
<span className="font-display text-3xl font-semibold text-cream">Leopost</span>
</Link>
{/* Quote */}
<div className="relative z-10 max-w-md">
<div className="w-12 h-1 bg-accent mb-8"></div>
<blockquote className="font-display text-3xl text-cream/90 leading-snug font-medium italic">
"Finalmente un assistente che capisce il mio brand e mi fa risparmiare ore ogni settimana."
</blockquote>
<div className="mt-8 flex items-center gap-4">
<div className="w-12 h-12 rounded-full bg-cream/20 flex items-center justify-center">
<span className="font-display text-cream font-semibold">MR</span>
</div>
<div>
<p className="text-cream font-medium">Marco Rossi</p>
<p className="text-cream/60 text-sm">Social Media Manager</p>
</div>
</div>
</div>
{/* Footer */}
<p className="text-cream/40 text-sm relative z-10">
© 2026 Leopost
</p>
</div>
{/* Right side - Form */}
<div className="flex-1 flex flex-col">
{/* Mobile header */}
<div className="lg:hidden p-6 border-b border-editorial">
<Link href="/" className="font-display text-2xl font-semibold">
Leopost
</Link>
</div>
{/* Form container */}
<div className="flex-1 flex items-center justify-center p-6 sm:p-12">
<div className="w-full max-w-md opacity-0 animate-fade-up">
{children}
</div>
</div>
</div>
</div>
)

View File

@@ -1,30 +1,48 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { LoginForm } from '@/components/auth/login-form'
import { GoogleSignInButton } from '@/components/auth/google-button'
import Link from 'next/link'
export default function LoginPage() {
return (
<Card>
<CardHeader className="text-center">
<CardTitle>Accedi a Leopost</CardTitle>
<CardDescription>
<div>
{/* Header */}
<div className="mb-8">
<span className="editorial-tag">Bentornato</span>
<h1 className="mt-4 text-3xl font-display font-semibold">
Accedi a Leopost
</h1>
<p className="mt-2 text-ink-light">
Inserisci le tue credenziali per continuare
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<GoogleSignInButton />
</p>
</div>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-gray-300" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-white px-2 text-gray-500">oppure</span>
</div>
{/* Google Sign In */}
<GoogleSignInButton />
{/* Divider */}
<div className="relative my-8">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-editorial"></div>
</div>
<div className="relative flex justify-center">
<span className="bg-cream px-4 text-sm text-muted uppercase tracking-wide">
oppure
</span>
</div>
</div>
<LoginForm />
</CardContent>
</Card>
{/* Email/Password Form */}
<LoginForm />
{/* Footer links */}
<div className="mt-8 pt-6 border-t border-editorial text-center">
<p className="text-sm text-ink-light">
Non hai un account?{' '}
<Link href="/register/" className="text-accent hover:underline font-medium">
Registrati gratis
</Link>
</p>
</div>
</div>
)
}

View File

@@ -1,30 +1,48 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { RegisterForm } from '@/components/auth/register-form'
import { GoogleSignInButton } from '@/components/auth/google-button'
import Link from 'next/link'
export default function RegisterPage() {
return (
<Card>
<CardHeader className="text-center">
<CardTitle>Crea il tuo account</CardTitle>
<CardDescription>
Inizia a usare Leopost gratuitamente
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<GoogleSignInButton />
<div>
{/* Header */}
<div className="mb-8">
<span className="editorial-tag">Inizia gratis</span>
<h1 className="mt-4 text-3xl font-display font-semibold">
Crea il tuo account
</h1>
<p className="mt-2 text-ink-light">
Inizia a gestire i tuoi social in modo intelligente
</p>
</div>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-gray-300" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-white px-2 text-gray-500">oppure</span>
</div>
{/* Google Sign In */}
<GoogleSignInButton />
{/* Divider */}
<div className="relative my-8">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-editorial"></div>
</div>
<div className="relative flex justify-center">
<span className="bg-cream px-4 text-sm text-muted uppercase tracking-wide">
oppure
</span>
</div>
</div>
<RegisterForm />
</CardContent>
</Card>
{/* Email/Password Form */}
<RegisterForm />
{/* Footer links */}
<div className="mt-8 pt-6 border-t border-editorial text-center">
<p className="text-sm text-ink-light">
Hai già un account?{' '}
<Link href="/login/" className="text-accent hover:underline font-medium">
Accedi
</Link>
</p>
</div>
</div>
)
}

View File

@@ -1,6 +1,6 @@
import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import Link from 'next/link'
export default async function DashboardPage() {
const supabase = await createClient()
@@ -32,81 +32,103 @@ export default async function DashboardPage() {
} | null
return (
<div className="space-y-6">
<div className="space-y-10 opacity-0 animate-fade-up">
{/* Header */}
<div>
<h1 className="text-2xl font-bold text-gray-900">Dashboard</h1>
<p className="text-gray-500">Benvenuto in Leopost</p>
<span className="editorial-tag">Dashboard</span>
<h1 className="mt-4 text-4xl font-display font-semibold">
Bentornato in Leopost
</h1>
<p className="mt-2 text-ink-light text-lg">
Gestisci i tuoi contenuti social da qui.
</p>
</div>
{/* Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card>
<CardHeader>
<CardTitle className="text-lg">Il tuo piano</CardTitle>
<CardDescription>
{/* Plan Card */}
<div className="card-editorial">
<div className="flex items-start justify-between mb-4">
<h2 className="font-display text-xl font-semibold">Il tuo piano</h2>
<span className="editorial-tag">
{profile?.plans?.display_name_it || 'Gratuito'}
</CardDescription>
</CardHeader>
<CardContent>
<ul className="space-y-2 text-sm text-gray-600">
<li>
<span className="font-medium">{features?.posts_per_month || 10}</span> post/mese
</li>
<li>
<span className="font-medium">{features?.social_accounts || 1}</span> account social
</li>
<li>
<span className="font-medium">{features?.ai_models?.length || 1}</span> modelli AI
</li>
</ul>
</CardContent>
</Card>
</span>
</div>
<ul className="space-y-3 text-ink-light">
<li className="flex justify-between">
<span>Post al mese</span>
<span className="font-medium text-ink">{features?.posts_per_month || 10}</span>
</li>
<li className="flex justify-between">
<span>Account social</span>
<span className="font-medium text-ink">{features?.social_accounts || 1}</span>
</li>
<li className="flex justify-between">
<span>Modelli AI</span>
<span className="font-medium text-ink">{features?.ai_models?.length || 1}</span>
</li>
</ul>
<div className="mt-6 pt-4 border-t border-editorial">
<Link
href="/subscription/"
className="text-sm text-accent hover:underline font-medium"
>
Cambia piano
</Link>
</div>
</div>
<Card>
<CardHeader>
<CardTitle className="text-lg">Prossimi passi</CardTitle>
<CardDescription>
Completa la configurazione
</CardDescription>
</CardHeader>
<CardContent>
<ul className="space-y-2 text-sm">
<li className="flex items-center gap-2">
<span className="w-5 h-5 rounded-full bg-green-100 text-green-600 flex items-center justify-center text-xs">
</span>
Account creato
</li>
<li className="flex items-center gap-2 text-gray-400">
<span className="w-5 h-5 rounded-full bg-gray-100 flex items-center justify-center text-xs">
2
</span>
Collega social (Phase 2)
</li>
<li className="flex items-center gap-2 text-gray-400">
<span className="w-5 h-5 rounded-full bg-gray-100 flex items-center justify-center text-xs">
3
</span>
Configura brand (Phase 3)
</li>
</ul>
</CardContent>
</Card>
{/* Onboarding Card */}
<div className="card-editorial">
<h2 className="font-display text-xl font-semibold mb-4">Prossimi passi</h2>
<ul className="space-y-4">
<li className="flex items-start gap-3">
<span className="w-6 h-6 flex items-center justify-center bg-success text-white text-xs font-medium">
</span>
<div>
<p className="font-medium text-ink">Account creato</p>
<p className="text-sm text-ink-light">Completo</p>
</div>
</li>
<li className="flex items-start gap-3">
<span className="w-6 h-6 flex items-center justify-center bg-cream-dark text-ink-muted text-xs font-medium">
2
</span>
<div>
<p className="font-medium text-ink-muted">Collega social</p>
<p className="text-sm text-ink-muted">Prossimamente</p>
</div>
</li>
<li className="flex items-start gap-3">
<span className="w-6 h-6 flex items-center justify-center bg-cream-dark text-ink-muted text-xs font-medium">
3
</span>
<div>
<p className="font-medium text-ink-muted">Configura brand</p>
<p className="text-sm text-ink-muted">Prossimamente</p>
</div>
</li>
</ul>
</div>
<Card>
<CardHeader>
<CardTitle className="text-lg">Attivita</CardTitle>
<CardDescription>
Le tue statistiche
</CardDescription>
</CardHeader>
<CardContent>
<div className="text-center py-4 text-gray-400 text-sm">
Nessuna attivita ancora.
<br />
Inizia collegando un account social!
{/* Activity Card */}
<div className="card-editorial">
<h2 className="font-display text-xl font-semibold mb-4">Attività</h2>
<div className="py-8 text-center">
<div className="w-16 h-16 mx-auto mb-4 bg-cream-dark flex items-center justify-center">
<svg className="w-8 h-8 text-ink-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</div>
</CardContent>
</Card>
<p className="text-ink-muted text-sm">
Nessuna attività ancora.
</p>
<p className="text-ink-muted text-sm mt-1">
Collega un account social per iniziare!
</p>
</div>
</div>
</div>
</div>
)

View File

@@ -31,25 +31,25 @@ export default async function DashboardLayout({
.single()
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-cream">
{/* Header */}
<header className="bg-white border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<header className="bg-white border-b border-editorial sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-6">
<div className="flex justify-between items-center h-16">
<div className="flex items-center gap-8">
<Link href="/dashboard" className="text-xl font-bold text-blue-600">
<div className="flex items-center gap-10">
<Link href="/dashboard/" className="font-display text-xl font-semibold text-ink">
Leopost
</Link>
<nav className="hidden md:flex items-center gap-4">
<nav className="hidden md:flex items-center gap-6">
<Link
href="/dashboard"
className="text-gray-600 hover:text-gray-900 text-sm font-medium"
href="/dashboard/"
className="text-ink-light hover:text-ink transition-colors text-sm font-medium"
>
Dashboard
</Link>
<Link
href="/subscription"
className="text-gray-600 hover:text-gray-900 text-sm font-medium"
href="/subscription/"
className="text-ink-light hover:text-ink transition-colors text-sm font-medium"
>
Piano
</Link>
@@ -64,7 +64,7 @@ export default async function DashboardLayout({
</header>
{/* Main content */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<main className="max-w-7xl mx-auto px-6 py-10">
{children}
</main>
</div>

View File

@@ -29,7 +29,7 @@ export default async function SubscriptionPage() {
if (plansError || !plans) {
return (
<div className="text-center py-12">
<p className="text-red-600">Errore nel caricamento dei piani</p>
<p className="text-error">Errore nel caricamento dei piani</p>
</div>
)
}
@@ -41,19 +41,23 @@ export default async function SubscriptionPage() {
})
return (
<div className="space-y-8">
<div className="space-y-12 opacity-0 animate-fade-up">
{/* Header */}
<div>
<h1 className="text-2xl font-bold text-gray-900">Il tuo abbonamento</h1>
<p className="text-gray-500 mt-1">
Scegli il piano piu adatto alle tue esigenze
<span className="editorial-tag">Abbonamento</span>
<h1 className="mt-4 text-4xl font-display font-semibold">
Scegli il tuo piano
</h1>
<p className="mt-2 text-ink-light text-lg">
Trova il piano più adatto alle tue esigenze
</p>
</div>
{/* Info banner */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<p className="text-sm text-blue-800">
<strong>Nota:</strong> Il pagamento verra implementato nelle prossime versioni.
Per ora puoi passare liberamente tra i piani per testare le funzionalita.
<div className="p-5 bg-accent-light border-l-4 border-accent">
<p className="text-sm text-ink">
<strong>Nota:</strong> Il pagamento verrà implementato nelle prossime versioni.
Per ora puoi passare liberamente tra i piani per testare le funzionalità.
</p>
</div>
@@ -69,19 +73,19 @@ export default async function SubscriptionPage() {
</div>
{/* Feature comparison */}
<div className="mt-12">
<h2 className="text-xl font-semibold text-gray-900 mb-4">
Confronto funzionalita
<div>
<h2 className="text-2xl font-display font-semibold mb-6">
Confronto funzionalità
</h2>
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<table className="w-full">
<thead>
<tr className="border-b">
<th className="text-left py-3 px-4 font-medium text-gray-600">
Funzionalita
<tr className="border-b-2 border-ink">
<th className="text-left py-4 px-4 font-display font-semibold text-ink">
Funzionalità
</th>
{sortedPlans.map((plan) => (
<th key={plan.id} className="text-center py-3 px-4 font-medium text-gray-900">
<th key={plan.id} className="text-center py-4 px-4 font-display font-semibold text-ink">
{plan.display_name_it}
</th>
))}
@@ -106,17 +110,17 @@ export default async function SubscriptionPage() {
<FeatureRow
feature="Generazione immagini"
plans={sortedPlans as Plan[]}
getValue={(p) => (p.features as PlanFeatures).image_generation ? '\u2713' : '\u2014'}
getValue={(p) => (p.features as PlanFeatures).image_generation ? '' : ''}
/>
<FeatureRow
feature="Automazione"
plans={sortedPlans as Plan[]}
getValue={(p) => {
const auto = (p.features as PlanFeatures).automation
if (auto === false) return '\u2014'
if (auto === false) return ''
if (auto === 'manual') return 'Manuale'
if (auto === 'full') return 'Completa'
return '\u2014'
return ''
}}
/>
</tbody>
@@ -125,14 +129,14 @@ export default async function SubscriptionPage() {
</div>
{/* FAQ */}
<div className="mt-12">
<h2 className="text-xl font-semibold text-gray-900 mb-4">
<div>
<h2 className="text-2xl font-display font-semibold mb-6">
Domande frequenti
</h2>
<div className="space-y-4">
<FaqItem
question="Posso cambiare piano in qualsiasi momento?"
answer="Si, puoi passare a un piano superiore o inferiore quando vuoi. Le modifiche sono immediate."
answer="Sì, puoi passare a un piano superiore o inferiore quando vuoi. Le modifiche sono immediate."
/>
<FaqItem
question="Cosa succede se supero i limiti del mio piano?"
@@ -140,7 +144,7 @@ export default async function SubscriptionPage() {
/>
<FaqItem
question="Come funziona il pagamento?"
answer="Il sistema di pagamento verra implementato a breve. Per ora tutti i piani sono disponibili gratuitamente per i test."
answer="Il sistema di pagamento verrà implementato a breve. Per ora tutti i piani sono disponibili gratuitamente per i test."
/>
</div>
</div>
@@ -158,10 +162,10 @@ function FeatureRow({
getValue: (plan: Plan) => string
}) {
return (
<tr className="border-b">
<td className="py-3 px-4 text-gray-600">{feature}</td>
<tr className="border-b border-editorial">
<td className="py-4 px-4 text-ink-light">{feature}</td>
{plans.map((plan) => (
<td key={plan.id} className="text-center py-3 px-4">
<td key={plan.id} className="text-center py-4 px-4 font-medium">
{getValue(plan)}
</td>
))}
@@ -171,9 +175,9 @@ function FeatureRow({
function FaqItem({ question, answer }: { question: string; answer: string }) {
return (
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-medium text-gray-900 mb-2">{question}</h3>
<p className="text-sm text-gray-600">{answer}</p>
<div className="card-editorial">
<h3 className="font-display font-semibold text-ink mb-2">{question}</h3>
<p className="text-ink-light">{answer}</p>
</div>
)
}

View File

@@ -1,26 +1,238 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
/* ==========================================================================
LEOPOST DESIGN SYSTEM - "Editorial Fresh"
========================================================================== */
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
/* Typography */
--font-body: var(--font-body), system-ui, sans-serif;
--font-display: var(--font-display), Georgia, serif;
/* Color Palette */
--color-cream: #FFFBF5;
--color-cream-dark: #F5F0E8;
--color-ink: #1A1A1A;
--color-ink-light: #4A4A4A;
--color-ink-muted: #7A7A7A;
/* Accent - Coral Red */
--color-accent: #E85A4F;
--color-accent-hover: #D14940;
--color-accent-light: #FFF0EE;
/* Semantic */
--color-background: var(--color-cream);
--color-foreground: var(--color-ink);
--color-muted: var(--color-ink-muted);
--color-border: #E5E0D8;
--color-border-strong: #D0C9BD;
/* Success/Error */
--color-success: #2D7A4F;
--color-success-light: #E8F5ED;
--color-error: #C53030;
--color-error-light: #FEE2E2;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
/* ==========================================================================
BASE STYLES
========================================================================== */
body {
background-color: var(--color-cream);
color: var(--color-ink);
font-family: var(--font-body);
font-size: 16px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Display Typography */
.font-display {
font-family: var(--font-display);
font-optical-sizing: auto;
}
.font-body {
font-family: var(--font-body);
}
/* Heading Styles */
h1, h2, h3, h4, h5, h6,
.heading {
font-family: var(--font-display);
font-weight: 600;
line-height: 1.2;
color: var(--color-ink);
letter-spacing: -0.02em;
}
/* ==========================================================================
EDITORIAL ELEMENTS
========================================================================== */
/* Decorative line */
.editorial-line {
width: 60px;
height: 3px;
background-color: var(--color-accent);
}
/* Pull quote style */
.pull-quote {
font-family: var(--font-display);
font-size: 1.5rem;
font-style: italic;
color: var(--color-ink-light);
border-left: 3px solid var(--color-accent);
padding-left: 1.5rem;
}
/* Label/Tag style */
.editorial-tag {
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--color-accent);
}
/* ==========================================================================
COMPONENT OVERRIDES
========================================================================== */
/* Buttons - Primary */
.btn-primary {
background-color: var(--color-ink);
color: var(--color-cream);
font-weight: 500;
padding: 0.75rem 1.5rem;
border-radius: 0;
transition: all 0.2s ease;
}
.btn-primary:hover {
background-color: var(--color-accent);
transform: translateY(-1px);
}
/* Buttons - Secondary */
.btn-secondary {
background-color: transparent;
color: var(--color-ink);
font-weight: 500;
padding: 0.75rem 1.5rem;
border: 2px solid var(--color-ink);
border-radius: 0;
transition: all 0.2s ease;
}
.btn-secondary:hover {
background-color: var(--color-ink);
color: var(--color-cream);
}
/* Cards */
.card-editorial {
background-color: white;
border: 1px solid var(--color-border);
padding: 2rem;
position: relative;
}
.card-editorial::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background-color: var(--color-accent);
}
/* Input fields */
input[type="text"],
input[type="email"],
input[type="password"],
textarea {
border: 1px solid var(--color-border);
border-radius: 0;
background-color: white;
padding: 0.75rem 1rem;
font-family: var(--font-body);
transition: border-color 0.2s ease;
}
input:focus,
textarea:focus {
outline: none;
border-color: var(--color-ink);
}
/* ==========================================================================
UTILITIES
========================================================================== */
/* Text colors */
.text-accent { color: var(--color-accent); }
.text-muted { color: var(--color-ink-muted); }
.text-ink { color: var(--color-ink); }
.text-ink-light { color: var(--color-ink-light); }
/* Background colors */
.bg-cream { background-color: var(--color-cream); }
.bg-cream-dark { background-color: var(--color-cream-dark); }
.bg-accent-light { background-color: var(--color-accent-light); }
/* Border colors */
.border-editorial { border-color: var(--color-border); }
/* ==========================================================================
ANIMATIONS
========================================================================== */
@keyframes fade-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-fade-up {
animation: fade-up 0.6s ease-out forwards;
}
.animate-fade-in {
animation: fade-in 0.4s ease-out forwards;
}
/* Staggered animations */
.stagger-1 { animation-delay: 0.1s; }
.stagger-2 { animation-delay: 0.2s; }
.stagger-3 { animation-delay: 0.3s; }
.stagger-4 { animation-delay: 0.4s; }
/* ==========================================================================
SELECTION & FOCUS
========================================================================== */
::selection {
background-color: var(--color-accent);
color: white;
}
:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}

View File

@@ -1,15 +1,17 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { DM_Sans, Fraunces } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
const dmSans = DM_Sans({
variable: "--font-body",
subsets: ["latin"],
display: "swap",
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
const fraunces = Fraunces({
variable: "--font-display",
subsets: ["latin"],
display: "swap",
});
export const metadata: Metadata = {
@@ -27,9 +29,7 @@ export default function RootLayout({
}>) {
return (
<html lang="it">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<body className={`${dmSans.variable} ${fraunces.variable} font-body antialiased`}>
{children}
</body>
</html>

View File

@@ -1,37 +1,194 @@
import Link from 'next/link'
import { Button } from '@/components/ui/button'
export default function Home() {
// Simple static landing page - no Supabase for now
return (
<main className="min-h-screen flex flex-col items-center justify-center p-8 bg-gradient-to-b from-blue-50 to-white">
<div className="text-center max-w-2xl">
<h1 className="text-5xl font-bold text-gray-900 mb-4">
Leopost
</h1>
<p className="text-xl text-gray-600 mb-8">
Il tuo social media manager potenziato dall&apos;AI.
<br />
Minimo sforzo, massima resa.
</p>
<div className="flex gap-4 justify-center">
<Link href="/register/">
<Button size="lg">
Inizia gratis
</Button>
<main className="min-h-screen bg-cream">
{/* Header */}
<header className="fixed top-0 left-0 right-0 z-50 bg-cream/90 backdrop-blur-sm border-b border-editorial">
<div className="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
<Link href="/" className="font-display text-2xl font-semibold tracking-tight">
Leopost
</Link>
<Link href="/login/">
<Button variant="outline" size="lg">
<nav className="flex items-center gap-6">
<Link
href="/login/"
className="text-ink-light hover:text-ink transition-colors text-sm font-medium"
>
Accedi
</Button>
</Link>
</Link>
<Link
href="/register/"
className="btn-primary text-sm"
>
Inizia gratis
</Link>
</nav>
</div>
</header>
<p className="mt-8 text-sm text-gray-500">
Nessuna carta richiesta. Piano gratuito disponibile.
</p>
</div>
{/* Hero Section */}
<section className="pt-32 pb-20 px-6">
<div className="max-w-6xl mx-auto">
<div className="grid lg:grid-cols-2 gap-16 items-center">
{/* Left Column - Text */}
<div className="opacity-0 animate-fade-up">
<span className="editorial-tag">Social Media Manager</span>
<h1 className="mt-6 text-5xl md:text-6xl lg:text-7xl font-display font-semibold leading-[1.1] tracking-tight">
Il tuo assistente
<br />
<span className="text-accent">editoriale</span>
<br />
potenziato dall&apos;AI
</h1>
<div className="editorial-line mt-8"></div>
<p className="mt-8 text-xl text-ink-light leading-relaxed max-w-lg">
Crea, programma e pubblica contenuti su tutti i tuoi canali social.
Minimo sforzo, massima resa.
</p>
<div className="mt-10 flex flex-col sm:flex-row gap-4">
<Link href="/register/" className="btn-primary text-center">
Inizia gratis
</Link>
<Link href="/login/" className="btn-secondary text-center">
Ho già un account
</Link>
</div>
<p className="mt-6 text-sm text-muted">
Nessuna carta richiesta · Piano gratuito per sempre
</p>
</div>
{/* Right Column - Visual */}
<div className="opacity-0 animate-fade-up stagger-2 hidden lg:block">
<div className="relative">
{/* Decorative background */}
<div className="absolute -inset-4 bg-cream-dark rounded-sm -rotate-2"></div>
{/* Main card */}
<div className="card-editorial relative">
<div className="space-y-6">
{/* Fake chat message */}
<div className="flex gap-4">
<div className="w-10 h-10 rounded-full bg-accent/20 flex items-center justify-center flex-shrink-0">
<span className="text-accent font-display font-semibold">L</span>
</div>
<div>
<p className="font-medium text-sm">Leopost AI</p>
<p className="mt-1 text-ink-light">
Ho preparato 3 post per la tua pagina Instagram.
Vuoi che li programmi per la prossima settimana?
</p>
</div>
</div>
{/* Divider */}
<div className="border-t border-editorial"></div>
{/* Post preview cards */}
<div className="grid grid-cols-3 gap-3">
<div className="aspect-square bg-cream-dark rounded-sm flex items-center justify-center">
<span className="text-2xl">📸</span>
</div>
<div className="aspect-square bg-cream-dark rounded-sm flex items-center justify-center">
<span className="text-2xl"></span>
</div>
<div className="aspect-square bg-cream-dark rounded-sm flex items-center justify-center">
<span className="text-2xl">🎯</span>
</div>
</div>
{/* Action buttons */}
<div className="flex gap-3">
<button className="flex-1 py-2 text-sm font-medium bg-ink text-cream">
Programma tutti
</button>
<button className="flex-1 py-2 text-sm font-medium border border-editorial">
Modifica
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{/* Features Section */}
<section className="py-20 px-6 bg-cream-dark">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-16 opacity-0 animate-fade-up">
<span className="editorial-tag">Come funziona</span>
<h2 className="mt-4 text-4xl font-display font-semibold">
Tre passi verso la libertà editoriale
</h2>
</div>
<div className="grid md:grid-cols-3 gap-8">
{/* Feature 1 */}
<div className="opacity-0 animate-fade-up stagger-1">
<div className="text-5xl font-display font-semibold text-accent/30">01</div>
<h3 className="mt-4 text-xl font-display font-semibold">Connetti i tuoi canali</h3>
<p className="mt-2 text-ink-light">
Collega Facebook, Instagram e gli altri social in pochi click.
</p>
</div>
{/* Feature 2 */}
<div className="opacity-0 animate-fade-up stagger-2">
<div className="text-5xl font-display font-semibold text-accent/30">02</div>
<h3 className="mt-4 text-xl font-display font-semibold">Chatta con l&apos;AI</h3>
<p className="mt-2 text-ink-light">
Descrivi cosa vuoi comunicare. L&apos;AI crea contenuti su misura per te.
</p>
</div>
{/* Feature 3 */}
<div className="opacity-0 animate-fade-up stagger-3">
<div className="text-5xl font-display font-semibold text-accent/30">03</div>
<h3 className="mt-4 text-xl font-display font-semibold">Rilassati</h3>
<p className="mt-2 text-ink-light">
Leopost pubblica automaticamente. Tu pensa al tuo business.
</p>
</div>
</div>
</div>
</section>
{/* CTA Section */}
<section className="py-20 px-6">
<div className="max-w-3xl mx-auto text-center opacity-0 animate-fade-up">
<h2 className="text-4xl md:text-5xl font-display font-semibold">
Pronto a trasformare
<br />
la tua presenza social?
</h2>
<div className="editorial-line mx-auto mt-8"></div>
<p className="mt-8 text-xl text-ink-light">
Inizia oggi con il piano gratuito. Nessun vincolo.
</p>
<div className="mt-10">
<Link href="/register/" className="btn-primary inline-block">
Crea il tuo account
</Link>
</div>
</div>
</section>
{/* Footer */}
<footer className="py-8 px-6 border-t border-editorial">
<div className="max-w-6xl mx-auto flex flex-col md:flex-row items-center justify-between gap-4">
<div className="font-display text-lg font-semibold">Leopost</div>
<p className="text-sm text-muted">
© 2026 Leopost. Tutti i diritti riservati.
</p>
</div>
</footer>
</main>
)
}

View File

@@ -62,10 +62,10 @@ export function GoogleSignInButton() {
variant="outline"
onClick={handleGoogleSignIn}
disabled={loading}
className="w-full flex items-center justify-center gap-2"
className="w-full flex items-center justify-center gap-3 h-12 text-base"
>
<GoogleIcon className="w-5 h-5" />
{loading ? 'Reindirizzamento...' : 'Accedi con Google'}
{loading ? 'Reindirizzamento...' : 'Continua con Google'}
</Button>
)
}

View File

@@ -43,15 +43,15 @@ export function LoginForm() {
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-5">
{error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-md">
<p className="text-red-800 text-sm">{error}</p>
<div className="p-4 bg-error-light border-l-4 border-error">
<p className="text-error text-sm">{error}</p>
</div>
)}
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
<label htmlFor="email" className="block text-sm font-medium text-ink mb-2">
Email
</label>
<Input
@@ -67,7 +67,7 @@ export function LoginForm() {
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
<label htmlFor="password" className="block text-sm font-medium text-ink mb-2">
Password
</label>
<Input
@@ -83,21 +83,14 @@ export function LoginForm() {
</div>
<div className="flex items-center justify-end">
<Link href="/reset-password" className="text-sm text-blue-600 hover:underline">
<Link href="/reset-password/" className="text-sm text-accent hover:underline">
Password dimenticata?
</Link>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? 'Accesso...' : 'Accedi'}
<Button type="submit" className="w-full h-12 text-base" disabled={loading}>
{loading ? 'Accesso in corso...' : 'Accedi'}
</Button>
<p className="text-center text-sm text-gray-600">
Non hai un account?{' '}
<Link href="/register" className="text-blue-600 hover:underline">
Registrati
</Link>
</p>
</form>
)
}

View File

@@ -81,12 +81,15 @@ export function RegisterForm() {
if (success) {
return (
<div className="text-center">
<div className="mb-4 p-4 bg-green-50 border border-green-200 rounded-md">
<p className="text-green-800">
Registrazione completata! Controlla la tua email per confermare l&apos;account.
<div className="mb-6 p-6 bg-success-light border-l-4 border-success">
<p className="text-success font-medium">
Registrazione completata!
</p>
<p className="mt-2 text-ink-light text-sm">
Controlla la tua email per confermare l&apos;account.
</p>
</div>
<Link href="/login" className="text-blue-600 hover:underline">
<Link href="/login/" className="text-accent hover:underline font-medium">
Torna al login
</Link>
</div>
@@ -94,15 +97,15 @@ export function RegisterForm() {
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-5">
{error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-md">
<p className="text-red-800 text-sm">{error}</p>
<div className="p-4 bg-error-light border-l-4 border-error">
<p className="text-error text-sm">{error}</p>
</div>
)}
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
<label htmlFor="email" className="block text-sm font-medium text-ink mb-2">
Email
</label>
<Input
@@ -118,7 +121,7 @@ export function RegisterForm() {
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
<label htmlFor="password" className="block text-sm font-medium text-ink mb-2">
Password
</label>
<Input
@@ -133,15 +136,15 @@ export function RegisterForm() {
error={!!fieldErrors.password}
/>
{fieldErrors.password && (
<p className="mt-1 text-sm text-red-600">{fieldErrors.password}</p>
<p className="mt-2 text-sm text-error">{fieldErrors.password}</p>
)}
<p className="mt-1 text-xs text-gray-500">
<p className="mt-2 text-xs text-muted">
Almeno 8 caratteri, 1 numero, 1 maiuscola
</p>
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
<label htmlFor="confirmPassword" className="block text-sm font-medium text-ink mb-2">
Conferma Password
</label>
<Input
@@ -156,20 +159,13 @@ export function RegisterForm() {
error={!!fieldErrors.confirmPassword}
/>
{fieldErrors.confirmPassword && (
<p className="mt-1 text-sm text-red-600">{fieldErrors.confirmPassword}</p>
<p className="mt-2 text-sm text-error">{fieldErrors.confirmPassword}</p>
)}
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? 'Registrazione...' : 'Registrati'}
<Button type="submit" className="w-full h-12 text-base" disabled={loading}>
{loading ? 'Registrazione...' : 'Crea account'}
</Button>
<p className="text-center text-sm text-gray-600">
Hai gia un account?{' '}
<Link href="/login" className="text-blue-600 hover:underline">
Accedi
</Link>
</p>
</form>
)
}

View File

@@ -22,19 +22,36 @@ export function UserNav({ email, planName }: UserNavProps) {
router.refresh()
}
// Get initials from email
const initials = email
.split('@')[0]
.split(/[._-]/)
.slice(0, 2)
.map(part => part.charAt(0).toUpperCase())
.join('')
return (
<div className="flex items-center gap-4">
<div className="text-sm text-right">
<p className="font-medium">{email}</p>
{/* Avatar */}
<div className="w-9 h-9 bg-ink flex items-center justify-center">
<span className="text-cream text-sm font-medium">{initials}</span>
</div>
{/* Info */}
<div className="hidden sm:block text-sm text-right">
<p className="font-medium text-ink">{email}</p>
{planName && (
<p className="text-gray-500 text-xs capitalize">Piano {planName}</p>
<p className="text-ink-muted text-xs">Piano {planName}</p>
)}
</div>
{/* Logout button */}
<Button
variant="outline"
variant="ghost"
size="sm"
onClick={handleSignOut}
disabled={loading}
className="text-ink-light hover:text-ink"
>
{loading ? 'Uscita...' : 'Esci'}
</Button>

View File

@@ -1,9 +1,8 @@
'use client'
import { Plan, PlanFeatures } from '@/types/database'
import { formatFeatureValue, formatPrice, FEATURE_LABELS, getPlanBadgeColor } from '@/lib/plans'
import { formatFeatureValue, formatPrice, FEATURE_LABELS } from '@/lib/plans'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { switchPlan } from '@/app/actions/subscription'
import { useTransition } from 'react'
import { useRouter } from 'next/navigation'
@@ -41,51 +40,57 @@ export function PlanCard({ plan, isCurrentPlan, onPlanChange }: PlanCardProps) {
'automation',
]
const isPro = plan.name === 'pro'
return (
<Card className={`relative ${isCurrentPlan ? 'ring-2 ring-blue-500' : ''}`}>
<div className={`relative bg-white border ${isCurrentPlan ? 'border-accent' : 'border-editorial'} ${isPro ? 'ring-2 ring-ink' : ''}`}>
{/* Top accent bar */}
<div className={`h-1 ${isPro ? 'bg-ink' : isCurrentPlan ? 'bg-accent' : 'bg-cream-dark'}`}></div>
{/* Current plan badge */}
{isCurrentPlan && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
<span className="bg-blue-500 text-white text-xs font-medium px-3 py-1 rounded-full">
<span className="bg-accent text-white text-xs font-medium px-3 py-1">
Piano attuale
</span>
</div>
)}
<CardHeader className="text-center pt-8">
<div className="mb-2">
<span className={`inline-block px-3 py-1 text-xs font-medium rounded-full border ${getPlanBadgeColor(plan.name)}`}>
{plan.display_name_it}
</span>
</div>
<CardTitle className="text-3xl">
{/* Header */}
<div className="p-6 text-center border-b border-editorial">
<span className="editorial-tag">
{plan.display_name_it}
</span>
<div className="mt-4 font-display text-4xl font-semibold text-ink">
{formatPrice(plan.price_monthly)}
</CardTitle>
<CardDescription>
</div>
<p className="mt-2 text-sm text-ink-light">
{plan.name === 'free' && 'Perfetto per iniziare'}
{plan.name === 'creator' && 'Per creator seri'}
{plan.name === 'pro' && 'Per professionisti'}
</CardDescription>
</CardHeader>
</p>
</div>
<CardContent>
<ul className="space-y-3">
{/* Features */}
<div className="p-6">
<ul className="space-y-4">
{displayFeatures.map((featureKey) => {
const value = features[featureKey]
const isIncluded = value !== false
return (
<li key={featureKey} className="flex items-center gap-2">
<span className={`w-5 h-5 rounded-full flex items-center justify-center text-xs ${
isIncluded ? 'bg-green-100 text-green-600' : 'bg-gray-100 text-gray-400'
<li key={featureKey} className="flex items-center gap-3">
<span className={`w-5 h-5 flex items-center justify-center text-xs font-medium ${
isIncluded ? 'bg-success text-white' : 'bg-cream-dark text-ink-muted'
}`}>
{isIncluded ? '\u2713' : '\u2014'}
{isIncluded ? '' : ''}
</span>
<span className="text-sm">
<span className="font-medium">
<span className="font-medium text-ink">
{formatFeatureValue(featureKey, value)}
</span>
{' '}
<span className="text-gray-500">
<span className="text-ink-light">
{FEATURE_LABELS[featureKey].toLowerCase()}
</span>
</span>
@@ -93,9 +98,10 @@ export function PlanCard({ plan, isCurrentPlan, onPlanChange }: PlanCardProps) {
)
})}
</ul>
</CardContent>
</div>
<CardFooter>
{/* Footer */}
<div className="p-6 pt-0">
{isCurrentPlan ? (
<Button variant="outline" className="w-full" disabled>
Piano attuale
@@ -105,14 +111,14 @@ export function PlanCard({ plan, isCurrentPlan, onPlanChange }: PlanCardProps) {
className="w-full"
onClick={handleSwitchPlan}
disabled={isPending}
variant={plan.name === 'pro' ? 'default' : 'outline'}
variant={isPro ? 'default' : 'outline'}
>
{isPending ? 'Cambio in corso...' : (
plan.price_monthly === 0 ? 'Passa a Gratuito' : `Passa a ${plan.display_name_it}`
)}
</Button>
)}
</CardFooter>
</Card>
</div>
</div>
)
}

View File

@@ -2,7 +2,7 @@ import { forwardRef, ButtonHTMLAttributes } from 'react'
import { cn } from '@/lib/utils'
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'outline' | 'ghost'
variant?: 'default' | 'outline' | 'ghost' | 'accent'
size?: 'default' | 'sm' | 'lg'
}
@@ -11,18 +11,25 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
return (
<button
className={cn(
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
'inline-flex items-center justify-center font-medium transition-all duration-200',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2',
'disabled:pointer-events-none disabled:opacity-50',
// Variants
{
'bg-blue-600 text-white hover:bg-blue-700': variant === 'default',
'border border-gray-300 bg-white hover:bg-gray-50': variant === 'outline',
'hover:bg-gray-100': variant === 'ghost',
// Primary - Black button
'bg-ink text-cream hover:bg-accent': variant === 'default',
// Outline - Border button
'border-2 border-ink bg-transparent text-ink hover:bg-ink hover:text-cream': variant === 'outline',
// Ghost - No background
'text-ink hover:bg-cream-dark': variant === 'ghost',
// Accent - Coral button
'bg-accent text-white hover:bg-accent-hover': variant === 'accent',
},
// Sizes - No border radius for editorial style
{
'h-10 px-4 py-2': size === 'default',
'h-8 px-3 text-sm': size === 'sm',
'h-12 px-6 text-lg': size === 'lg',
'h-11 px-5 py-2': size === 'default',
'h-9 px-4 text-sm': size === 'sm',
'h-13 px-8 text-lg': size === 'lg',
},
className
)}

View File

@@ -10,11 +10,12 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
return (
<input
className={cn(
'flex h-10 w-full rounded-md border bg-white px-3 py-2 text-sm',
'placeholder:text-gray-400',
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1',
'flex h-11 w-full border bg-white px-4 py-2 text-base',
'placeholder:text-ink-muted',
'focus:outline-none focus:border-ink',
'disabled:cursor-not-allowed disabled:opacity-50',
error ? 'border-red-500' : 'border-gray-300',
'transition-colors duration-200',
error ? 'border-error' : 'border-editorial',
className
)}
ref={ref}