Add i18n foundation (fa-IR/en-US) and localize landing + auth.
Introduce path-prefixed locale routing under app/[lang] with a middleware that detects locale from cookie/Accept-Language (default fa-IR) and redirects. Add fa-IR (source of truth) and en-US dictionaries, a server getDictionary, a client I18nProvider/useT, locale-aware Link + router helpers, and a language switcher. The root [lang] layout sets html lang/dir and the per-locale font (Peyda for fa, Inter for en). Landing sections and the login/register/auth shell now read all copy from the dictionaries; dashboard localization follows in a later commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { peyda } from '@/app/fonts';
|
||||
import { Link } from '@/i18n/Link';
|
||||
import { useT } from '@/i18n/I18nProvider';
|
||||
import { LanguageSwitcher } from '@/i18n/LanguageSwitcher';
|
||||
import { Logo } from '@/components/landing/Logo';
|
||||
|
||||
// Cinematic auth shell that echoes the Abrban landing: a calm blue sky (the
|
||||
// "clear sky" finale), soft drifting clouds, a warm sun glow, the brand logo,
|
||||
// and the form inside a frosted dark-glass panel. Persian / RTL.
|
||||
// and the form inside a frosted dark-glass panel. Direction is inherited from
|
||||
// the locale-aware root layout.
|
||||
export function AuthShell({
|
||||
icon,
|
||||
title,
|
||||
@@ -23,11 +27,9 @@ export function AuthShell({
|
||||
altHref: string;
|
||||
altLabel: string;
|
||||
}) {
|
||||
const t = useT();
|
||||
return (
|
||||
<div
|
||||
dir="rtl"
|
||||
className={`${peyda.variable} font-peyda relative min-h-screen w-full overflow-hidden bg-gradient-to-b from-[#1b6fd0] via-[#589add] to-[#cfe6f7] text-white`}
|
||||
>
|
||||
<div className="relative min-h-screen w-full overflow-hidden bg-gradient-to-b from-[#1b6fd0] via-[#589add] to-[#cfe6f7] text-white">
|
||||
{/* Sky atmosphere: warm sun glow, drifting clouds, soft vignette */}
|
||||
<div className="pointer-events-none absolute inset-0 overflow-hidden">
|
||||
<div className="absolute right-[10%] top-[-10%] h-80 w-80 rounded-full bg-[radial-gradient(circle,_rgba(255,244,214,0.9),_rgba(255,236,190,0.28)_45%,_transparent_70%)] blur-2xl" />
|
||||
@@ -47,15 +49,18 @@ export function AuthShell({
|
||||
|
||||
{/* Header */}
|
||||
<header className="relative z-10 mx-auto flex max-w-5xl items-center justify-between px-6 py-5">
|
||||
<Link href="/" aria-label="ابربان" className="transition hover:opacity-90">
|
||||
<Link href="/" aria-label={t.common.appName} className="transition hover:opacity-90">
|
||||
<Logo className="abrban-ink" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/"
|
||||
className="rounded-lg px-3 py-2 text-sm font-medium text-white/85 transition hover:text-white"
|
||||
>
|
||||
بازگشت به خانه
|
||||
</Link>
|
||||
<div className="flex items-center gap-1">
|
||||
<LanguageSwitcher className="text-white/85 hover:text-white" />
|
||||
<Link
|
||||
href="/"
|
||||
className="rounded-lg px-3 py-2 text-sm font-medium text-white/85 transition hover:text-white"
|
||||
>
|
||||
{t.auth.backHome}
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Form card */}
|
||||
|
||||
@@ -13,55 +13,17 @@ import {
|
||||
type LucideIcon,
|
||||
} from 'lucide-react';
|
||||
|
||||
export const BRAND = 'ابربان';
|
||||
|
||||
export const hero = {
|
||||
title: 'ابربان',
|
||||
tagline: 'زیرساخت ابری، در کنترل تو',
|
||||
subtitle:
|
||||
'اپلیکیشنت را در چند ثانیه روی کوبرنتیز منتشر کن — بدون دردسر سرور، بدون پیچیدگیِ DevOps.',
|
||||
ctaPrimary: 'رایگان شروع کن',
|
||||
ctaSecondary: 'ورود',
|
||||
};
|
||||
|
||||
interface Feature {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
desc: string;
|
||||
}
|
||||
|
||||
export const features: Feature[] = [
|
||||
{ icon: Rocket, title: 'دیپلوی برقآسا', desc: 'Node.js، لاراول و وردپرس را با یک کلیک منتشر کن؛ بیلد و انتشار خودکار.' },
|
||||
{ icon: Database, title: 'دیتابیس مدیریتشده', desc: 'PostgreSQL، MySQL، Redis، RabbitMQ و Elasticsearch، آماده و پایدار.' },
|
||||
{ icon: ShieldCheck, title: 'SSL خودکار', desc: 'دامنهٔ اختصاصی وصل کن؛ گواهی SSL خودکار صادر و تمدید میشود.' },
|
||||
{ icon: Link2, title: 'لینک پیشنمایش', desc: 'برای هر دیپلوی یک Preview URL پایدار روی TLS بگیر و سریع تست کن.' },
|
||||
{ icon: ScrollText, title: 'لاگ زنده', desc: 'لاگ بیلد و اجرای اپ را همان لحظه و بهصورت زنده دنبال کن.' },
|
||||
{ icon: History, title: 'اسنپشات و بکاپ', desc: 'از وضعیت اپ اسنپشات بگیر و هر زمان خواستی بازگردان.' },
|
||||
{ icon: CreditCard, title: 'بیلینگ شفاف', desc: 'کیفپول پیشپرداخت، فاکتور دقیق و مصرف لحظهای — بدون سورپرایز.' },
|
||||
{ icon: LifeBuoy, title: 'پشتیبانی فارسی', desc: 'تیم پشتیبانی و سیستم تیکت، فارسی و همیشه کنارت.' },
|
||||
// Icons stay in code (they aren't translatable); the matching copy lives in the
|
||||
// i18n dictionaries under `landing.features` / `landing.trust`, index-aligned.
|
||||
export const featureIcons: LucideIcon[] = [
|
||||
Rocket,
|
||||
Database,
|
||||
ShieldCheck,
|
||||
Link2,
|
||||
ScrollText,
|
||||
History,
|
||||
CreditCard,
|
||||
LifeBuoy,
|
||||
];
|
||||
|
||||
interface Step {
|
||||
n: string;
|
||||
title: string;
|
||||
desc: string;
|
||||
}
|
||||
|
||||
export const steps: Step[] = [
|
||||
{ n: '۱', title: 'کدت را بده', desc: 'ریپازیتوری یا اپت را وصل کن.' },
|
||||
{ n: '۲', title: 'ما میسازیم', desc: 'ابربان بهصورت خودکار بیلد و روی کوبرنتیز دیپلوی میکند.' },
|
||||
{ n: '۳', title: 'آنلاین شو', desc: 'دامنه و SSL آماده است؛ اپت زنده میشود.' },
|
||||
];
|
||||
|
||||
interface Kpi {
|
||||
icon: LucideIcon;
|
||||
kpi: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const trust: Kpi[] = [
|
||||
{ icon: Zap, kpi: '< ۶۰ ثانیه', label: 'میانگین زمان دیپلوی' },
|
||||
{ icon: Server, kpi: '۹۹٫۹٪', label: 'پایداریِ زیرساخت' },
|
||||
{ icon: ShieldCheck, kpi: 'TLS خودکار', label: 'امنیتِ پیشفرض' },
|
||||
{ icon: Globe, kpi: 'بومیِ ایران', label: 'بهینه برای کاربر ایرانی' },
|
||||
];
|
||||
export const trustIcons: LucideIcon[] = [Zap, Server, ShieldCheck, Globe];
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import { Reveal } from '../Reveal';
|
||||
import { features } from '../content';
|
||||
import { featureIcons } from '../content';
|
||||
import { useT } from '@/i18n/I18nProvider';
|
||||
|
||||
export function Features() {
|
||||
const f = useT().landing.features;
|
||||
return (
|
||||
<section className="relative px-6 py-28">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<Reveal className="mb-14 flex justify-center">
|
||||
<div className="abrban-panel rounded-3xl px-8 py-7 text-center">
|
||||
<h2 className="abrban-ink text-4xl font-bold text-white">هرچه برای ساختن لازم داری</h2>
|
||||
<p className="abrban-ink mt-4 text-lg text-white/85">یک جعبهابزارِ کامل برای انتشار و نگهداریِ اپ.</p>
|
||||
<h2 className="abrban-ink text-4xl font-bold text-white">{f.title}</h2>
|
||||
<p className="abrban-ink mt-4 text-lg text-white/85">{f.subtitle}</p>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{features.map((f, i) => (
|
||||
<Reveal key={f.title} delay={(i % 4) * 0.07}>
|
||||
<div className="abrban-panel group h-full rounded-2xl p-6 transition hover:-translate-y-1 hover:ring-1 hover:ring-primary-400/50">
|
||||
<div className="inline-flex h-12 w-12 items-center justify-center rounded-xl bg-primary-500/25 text-primary-100 ring-1 ring-primary-400/40">
|
||||
<f.icon className="h-6 w-6" />
|
||||
{f.items.map((item, i) => {
|
||||
const Icon = featureIcons[i];
|
||||
return (
|
||||
<Reveal key={item.title} delay={(i % 4) * 0.07}>
|
||||
<div className="abrban-panel group h-full rounded-2xl p-6 transition hover:-translate-y-1 hover:ring-1 hover:ring-primary-400/50">
|
||||
<div className="inline-flex h-12 w-12 items-center justify-center rounded-xl bg-primary-500/25 text-primary-100 ring-1 ring-primary-400/40">
|
||||
{Icon && <Icon className="h-6 w-6" />}
|
||||
</div>
|
||||
<h3 className="abrban-ink mt-5 text-lg font-bold text-white">{item.title}</h3>
|
||||
<p className="mt-2 text-sm leading-7 text-white/80">{item.desc}</p>
|
||||
</div>
|
||||
<h3 className="abrban-ink mt-5 text-lg font-bold text-white">{f.title}</h3>
|
||||
<p className="mt-2 text-sm leading-7 text-white/80">{f.desc}</p>
|
||||
</div>
|
||||
</Reveal>
|
||||
))}
|
||||
</Reveal>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { Link } from '@/i18n/Link';
|
||||
import { Reveal } from '../Reveal';
|
||||
import { useT } from '@/i18n/I18nProvider';
|
||||
|
||||
export function FinalCta() {
|
||||
const cta = useT().landing.finalCta;
|
||||
return (
|
||||
<section className="relative px-6 py-36">
|
||||
<Reveal className="mx-auto max-w-3xl">
|
||||
<div className="abrban-panel rounded-[2rem] px-8 py-14 text-center sm:px-14">
|
||||
<h2 className="abrban-ink text-4xl font-black leading-tight text-white sm:text-5xl">
|
||||
آسمان صاف است؛ وقتِ <span className="abrban-shimmer">زنده</span> کردنِ اپِ توست.
|
||||
{cta.titleLead}
|
||||
<span className="abrban-shimmer">{cta.titleHighlight}</span>
|
||||
{cta.titleTail}
|
||||
</h2>
|
||||
<p className="abrban-ink mt-6 text-lg text-white/85">
|
||||
همین حالا حسابت را بساز و اولین دیپلوی را تجربه کن.
|
||||
</p>
|
||||
<p className="abrban-ink mt-6 text-lg text-white/85">{cta.body}</p>
|
||||
<div className="mt-9 flex flex-col items-center justify-center gap-4 sm:flex-row">
|
||||
<Link
|
||||
href="/register"
|
||||
className="rounded-xl bg-primary-600 px-8 py-4 font-bold text-white shadow-lg shadow-primary-600/30 transition hover:bg-primary-500"
|
||||
>
|
||||
همین حالا شروع کن
|
||||
</Link>
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="rounded-xl border border-white/25 bg-white/10 px-8 py-4 font-bold text-white transition hover:bg-white/20"
|
||||
>
|
||||
داشبورد
|
||||
</Link>
|
||||
<Link
|
||||
href="/register"
|
||||
className="rounded-xl bg-primary-600 px-8 py-4 font-bold text-white shadow-lg shadow-primary-600/30 transition hover:bg-primary-500"
|
||||
>
|
||||
{cta.ctaPrimary}
|
||||
</Link>
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="rounded-xl border border-white/25 bg-white/10 px-8 py-4 font-bold text-white transition hover:bg-white/20"
|
||||
>
|
||||
{cta.ctaSecondary}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
import Link from 'next/link';
|
||||
'use client';
|
||||
|
||||
import { Link } from '@/i18n/Link';
|
||||
import { Logo } from '../Logo';
|
||||
import { useT } from '@/i18n/I18nProvider';
|
||||
|
||||
export function Footer() {
|
||||
const t = useT();
|
||||
return (
|
||||
<footer className="relative border-t border-white/15 bg-slate-950/35 px-6 py-12 backdrop-blur-md">
|
||||
<div className="mx-auto flex max-w-6xl flex-col items-center justify-between gap-6 sm:flex-row">
|
||||
<Logo className="abrban-ink" />
|
||||
<nav className="abrban-ink flex items-center gap-6 text-sm text-white/80">
|
||||
<Link href="/login" className="transition hover:text-white">
|
||||
ورود
|
||||
{t.common.login}
|
||||
</Link>
|
||||
<Link href="/register" className="transition hover:text-white">
|
||||
ثبتنام
|
||||
{t.common.register}
|
||||
</Link>
|
||||
<Link href="/dashboard" className="transition hover:text-white">
|
||||
داشبورد
|
||||
{t.common.dashboard}
|
||||
</Link>
|
||||
</nav>
|
||||
<p className="abrban-ink text-sm text-white/70">© ابربان — همهٔ حقوق محفوظ است</p>
|
||||
<p className="abrban-ink text-sm text-white/70">{t.landing.footer.copyright}</p>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { motion } from 'framer-motion';
|
||||
import { ArrowLeft, ChevronDown } from 'lucide-react';
|
||||
import { hero } from '../content';
|
||||
import { Link } from '@/i18n/Link';
|
||||
import { useT } from '@/i18n/I18nProvider';
|
||||
|
||||
export function Hero() {
|
||||
const t = useT();
|
||||
const hero = t.landing.hero;
|
||||
return (
|
||||
<section className="relative flex min-h-screen flex-col items-center justify-center px-6 text-center">
|
||||
<motion.div
|
||||
@@ -16,7 +18,7 @@ export function Hero() {
|
||||
>
|
||||
<span className="inline-flex items-center gap-2 rounded-full border border-white/20 bg-white/10 px-4 py-1.5 text-sm font-medium text-white/90 backdrop-blur-md">
|
||||
<span className="h-1.5 w-1.5 animate-pulse rounded-full bg-primary-400" />
|
||||
پلتفرم ابریِ خودسرویس
|
||||
{t.landing.badge}
|
||||
</span>
|
||||
|
||||
<h1 className="mt-7 text-6xl font-black tracking-tight sm:text-7xl md:text-8xl">
|
||||
@@ -34,7 +36,7 @@ export function Hero() {
|
||||
className="group inline-flex items-center gap-2 rounded-xl bg-primary-600 px-7 py-3.5 font-bold text-white shadow-lg shadow-primary-600/30 transition hover:bg-primary-500 hover:shadow-primary-500/40"
|
||||
>
|
||||
{hero.ctaPrimary}
|
||||
<ArrowLeft className="h-4 w-4 transition group-hover:-translate-x-1" />
|
||||
<ArrowLeft className="h-4 w-4 transition group-hover:-translate-x-1 ltr:rotate-180" />
|
||||
</Link>
|
||||
<Link
|
||||
href="/login"
|
||||
@@ -46,7 +48,7 @@ export function Hero() {
|
||||
</motion.div>
|
||||
|
||||
<div className="abrban-ink absolute bottom-10 flex flex-col items-center text-white/80">
|
||||
<span className="text-xs font-medium">به دلِ ابرها بزن</span>
|
||||
<span className="text-xs font-medium">{hero.scrollHint}</span>
|
||||
<ChevronDown className="abrban-scroll-hint mt-1 h-5 w-5" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import { Reveal } from '../Reveal';
|
||||
import { steps } from '../content';
|
||||
import { useT } from '@/i18n/I18nProvider';
|
||||
|
||||
export function HowItWorks() {
|
||||
const how = useT().landing.how;
|
||||
return (
|
||||
<section className="relative px-6 py-28">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<Reveal className="mb-16 flex justify-center">
|
||||
<h2 className="abrban-panel abrban-ink rounded-3xl px-8 py-5 text-4xl font-bold text-white">
|
||||
به سادگیِ سه قدم
|
||||
{how.title}
|
||||
</h2>
|
||||
</Reveal>
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-3">
|
||||
{steps.map((s, i) => (
|
||||
{how.steps.map((s, i) => (
|
||||
<Reveal key={s.n} delay={i * 0.1}>
|
||||
<div className="abrban-panel relative rounded-2xl p-8 text-center">
|
||||
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-primary-600/35 text-2xl font-black text-primary-100 ring-1 ring-primary-400/50">
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Link } from '@/i18n/Link';
|
||||
import { Logo } from '../Logo';
|
||||
import { useT } from '@/i18n/I18nProvider';
|
||||
import { LanguageSwitcher } from '@/i18n/LanguageSwitcher';
|
||||
|
||||
export function SiteHeader() {
|
||||
const t = useT();
|
||||
return (
|
||||
<motion.header
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
@@ -15,17 +18,18 @@ export function SiteHeader() {
|
||||
<div className="mx-auto flex max-w-6xl items-center justify-between px-6 py-5">
|
||||
<Logo className="abrban-ink" />
|
||||
<div className="flex items-center gap-2 rounded-xl bg-slate-950/30 p-1.5 backdrop-blur-md ring-1 ring-white/10">
|
||||
<LanguageSwitcher className="text-white/90 hover:text-white" />
|
||||
<Link
|
||||
href="/login"
|
||||
className="rounded-lg px-4 py-2 text-sm font-semibold text-white/90 transition hover:text-white"
|
||||
>
|
||||
ورود
|
||||
{t.common.login}
|
||||
</Link>
|
||||
<Link
|
||||
href="/register"
|
||||
className="rounded-lg bg-primary-600 px-4 py-2 text-sm font-semibold text-white shadow-lg shadow-primary-600/30 transition hover:bg-primary-500"
|
||||
>
|
||||
ثبتنام
|
||||
{t.common.register}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
'use client';
|
||||
|
||||
import { Reveal } from '../Reveal';
|
||||
import { trust } from '../content';
|
||||
import { trustIcons } from '../content';
|
||||
import { useT } from '@/i18n/I18nProvider';
|
||||
|
||||
export function Trust() {
|
||||
const trust = useT().landing.trust;
|
||||
return (
|
||||
<section className="relative px-6 py-28">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<Reveal className="mb-14 flex justify-center">
|
||||
<h2 className="abrban-panel abrban-ink rounded-3xl px-8 py-5 text-center text-3xl font-bold text-white sm:text-4xl">
|
||||
چرا تیمها به ابربان اعتماد میکنند
|
||||
{trust.title}
|
||||
</h2>
|
||||
</Reveal>
|
||||
|
||||
<div className="grid grid-cols-2 gap-5 lg:grid-cols-4">
|
||||
{trust.map((t, i) => (
|
||||
<Reveal key={t.label} delay={(i % 4) * 0.07}>
|
||||
<div className="abrban-panel rounded-2xl p-7 text-center">
|
||||
<t.icon className="mx-auto h-7 w-7 text-primary-200" />
|
||||
<div className="abrban-ink mt-4 text-3xl font-black text-white">{t.kpi}</div>
|
||||
<div className="mt-1 text-sm text-white/75">{t.label}</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
))}
|
||||
{trust.items.map((item, i) => {
|
||||
const Icon = trustIcons[i];
|
||||
return (
|
||||
<Reveal key={item.label} delay={(i % 4) * 0.07}>
|
||||
<div className="abrban-panel rounded-2xl p-7 text-center">
|
||||
{Icon && <Icon className="mx-auto h-7 w-7 text-primary-200" />}
|
||||
<div className="abrban-ink mt-4 text-3xl font-black text-white">{item.kpi}</div>
|
||||
<div className="mt-1 text-sm text-white/75">{item.label}</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
'use client';
|
||||
|
||||
import { Reveal } from '../Reveal';
|
||||
import { useT } from '@/i18n/I18nProvider';
|
||||
|
||||
export function Value() {
|
||||
const v = useT().landing.value;
|
||||
return (
|
||||
<section className="relative px-6 py-32">
|
||||
<Reveal className="mx-auto max-w-3xl">
|
||||
<div className="abrban-panel rounded-3xl px-8 py-12 text-center sm:px-12">
|
||||
<h2 className="abrban-ink text-3xl font-bold leading-snug text-white sm:text-4xl">
|
||||
یک پلتفرمِ <span className="text-primary-300">PaaS خودسرویس</span> روی کوبرنتیز —
|
||||
تمامِ قدرتِ زیرساختِ ابری، بدون پیچیدگیِ طوفانیاش.
|
||||
{v.lead}
|
||||
<span className="text-primary-300">{v.highlight}</span>
|
||||
{v.tail}
|
||||
</h2>
|
||||
<p className="abrban-ink mt-6 text-lg leading-9 text-white/85">
|
||||
ابربان لایههای سختِ DevOps را برایت مدیریت میکند تا فقط روی محصولت تمرکز کنی.
|
||||
</p>
|
||||
<p className="abrban-ink mt-6 text-lg leading-9 text-white/85">{v.body}</p>
|
||||
</div>
|
||||
</Reveal>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user