feat(website): Next.js 16 marketing website with RTL/Farsi

Marketing website for Meezi platform:
- Server-side rendered pages: home, demo, blog, pricing
- RTL/Farsi layout with Vazirmatn font
- SEO metadata and Open Graph tags
- proxy.ts for Next.js 16 middleware convention
- MEEZI_API_URL internal Docker network routing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-05-27 21:34:32 +03:30
parent 131ecdbbe6
commit d62bb8d3ad
84 changed files with 16985 additions and 0 deletions
@@ -0,0 +1,128 @@
"use client";
import { useEffect, useState } from "react";
import { X, Rocket } from "lucide-react";
import { useLocale } from "next-intl";
import { cn } from "@/lib/utils";
// 14 Khordad 1405 = June 4, 2026 (Tehran, UTC+3:30)
const LAUNCH_DATE = new Date("2026-06-04T00:00:00+03:30");
const DISMISS_KEY = "meezi_launch_banner_v2";
interface TimeLeft {
days: number;
hours: number;
minutes: number;
seconds: number;
expired: boolean;
}
function calcTimeLeft(): TimeLeft {
const diff = LAUNCH_DATE.getTime() - Date.now();
if (diff <= 0) return { days: 0, hours: 0, minutes: 0, seconds: 0, expired: true };
const days = Math.floor(diff / 86_400_000);
const hours = Math.floor((diff % 86_400_000) / 3_600_000);
const minutes = Math.floor((diff % 3_600_000) / 60_000);
const seconds = Math.floor((diff % 60_000) / 1_000);
return { days, hours, minutes, seconds, expired: false };
}
function pad(n: number) {
return String(n).padStart(2, "0");
}
export function LaunchCountdownSection() {
const locale = useLocale();
const isFa = locale === "fa";
const [visible, setVisible] = useState(false);
const [timeLeft, setTimeLeft] = useState<TimeLeft>(calcTimeLeft);
useEffect(() => {
if (localStorage.getItem(DISMISS_KEY) === "1") return;
if (calcTimeLeft().expired) return;
setVisible(true);
}, []);
useEffect(() => {
if (!visible) return;
const id = setInterval(() => {
const tl = calcTimeLeft();
setTimeLeft(tl);
if (tl.expired) setVisible(false);
}, 1_000);
return () => clearInterval(id);
}, [visible]);
if (!visible) return null;
function dismiss() {
localStorage.setItem(DISMISS_KEY, "1");
setVisible(false);
}
const { days, hours, minutes, seconds } = timeLeft;
return (
<section
dir={isFa ? "rtl" : "ltr"}
data-launch-countdown
aria-label={isFa ? "شمارش معکوس راه‌اندازی" : "Launch countdown"}
className="border-b border-gray-100 bg-gradient-to-b from-brand-50/80 to-white pt-24 pb-10 sm:pt-28 sm:pb-12"
>
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<div className="relative overflow-hidden rounded-2xl border border-brand-700/15 bg-white p-6 shadow-sm sm:p-8">
<button
onClick={dismiss}
aria-label={isFa ? "بستن" : "Dismiss"}
className="absolute end-4 top-4 rounded-lg p-1.5 text-gray-400 transition hover:bg-gray-100 hover:text-gray-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-700"
>
<X className="h-4 w-4" />
</button>
<div className="flex flex-col items-center gap-6 text-center">
<div className="flex flex-col items-center gap-2">
<span className="inline-flex items-center gap-1.5 rounded-full bg-brand-50 px-3 py-1 text-xs font-semibold uppercase tracking-wider text-brand-700">
<Rocket className="h-3.5 w-3.5" />
{isFa ? "راه‌اندازی رسمی" : "Official launch"}
</span>
<p className="max-w-2xl text-base font-medium text-gray-900 sm:text-lg">
{isFa
? "میزی رسماً ۱۴ خرداد ۱۴۰۵ برای همه کاربران راه‌اندازی می‌شود"
: "Meezi officially launches for all users on June 4, 2026"}
</p>
</div>
<div className="grid w-full max-w-md grid-cols-4 gap-3 sm:gap-4">
<CountdownUnit value={days} label={isFa ? "روز" : "Days"} />
<CountdownUnit value={hours} label={isFa ? "ساعت" : "Hours"} />
<CountdownUnit value={minutes} label={isFa ? "دقیقه" : "Min"} />
<CountdownUnit value={seconds} label={isFa ? "ثانیه" : "Sec"} />
</div>
<a
href="https://app.meezi.ir/register"
className={cn(
"inline-flex items-center justify-center rounded-xl bg-brand-700 px-6 py-3 text-sm font-semibold text-white",
"transition hover:bg-brand-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-700 focus-visible:ring-offset-2"
)}
>
{isFa ? "ثبت‌نام رایگان" : "Register free"}
</a>
</div>
</div>
</div>
</section>
);
}
function CountdownUnit({ value, label }: { value: number; label: string }) {
return (
<div className="flex flex-col items-center rounded-xl border border-gray-100 bg-gray-50 px-2 py-3 sm:px-3 sm:py-4">
<span className="font-mono text-2xl font-bold tabular-nums text-brand-700 sm:text-3xl">
{pad(value)}
</span>
<span className="mt-1 text-[11px] font-medium text-gray-500">{label}</span>
</div>
);
}