feat(frontend): add public landing page

This commit is contained in:
2026-04-30 17:06:25 +03:30
parent a5a7a01da0
commit bba1be1f71
7 changed files with 711 additions and 21 deletions

View File

@@ -24,6 +24,7 @@ import Timesheet from "./pages/Timesheet"
import Logs from "./pages/Logs"
import NotificationsPage from "./pages/Notifications"
import RateLimitPage from "./pages/RateLimit"
import Landing from "./pages/Landing"
import { isRateLimitActive } from "./lib/rateLimit"
const MainLayout = () => {
@@ -47,7 +48,7 @@ const MainLayout = () => {
);
};
const RootRedirect = () => {
const AppRedirect = () => {
if (isRateLimitActive()) {
return <Navigate to="/rate-limit" replace />
}
@@ -77,7 +78,8 @@ const router = createBrowserRouter([
</WorkspaceProvider>
),
children: [
{ path: "/", element: <RootRedirect /> },
{ path: "/", element: <Landing /> },
{ path: "/app", element: <AppRedirect /> },
{ path: "/auth", element: <Auth /> },
{ path: "/terms", element: <Terms /> },
{ path: "/rate-limit", element: <RateLimitPage /> },

View File

@@ -14,7 +14,7 @@ import {
History,
Tags,
} from 'lucide-react';
import { useWorkspace } from '../context/WorkspaceContext';
import { useOptionalWorkspace } from '../context/WorkspaceContext';
import { useTranslation } from '../hooks/useTranslation';
import { canWorkspace, WORKSPACE_LOGS_VIEW } from '../lib/permissions';
@@ -27,7 +27,8 @@ export const Sidebar = ({ mobileOpen = false, onMobileClose }: SidebarProps) =>
const [isCollapsed, setIsCollapsed] = useState(false);
const { t, lang } = useTranslation();
const { activeWorkspace } = useWorkspace();
const workspaceContext = useOptionalWorkspace();
const activeWorkspace = workspaceContext?.activeWorkspace ?? null;
const isRtl = lang === 'fa';
const canViewLogs = canWorkspace(activeWorkspace?.my_role, WORKSPACE_LOGS_VIEW);

View File

@@ -15,13 +15,15 @@ interface WorkspaceContextType {
isLoading: boolean
}
const WorkspaceContext = createContext<WorkspaceContextType | undefined>(undefined)
export const useWorkspace = () => {
const context = useContext(WorkspaceContext)
if (!context) throw new Error("useWorkspace must be used within a WorkspaceProvider")
return context
}
const WorkspaceContext = createContext<WorkspaceContextType | undefined>(undefined)
export const useWorkspace = () => {
const context = useContext(WorkspaceContext)
if (!context) throw new Error("useWorkspace must be used within a WorkspaceProvider")
return context
}
export const useOptionalWorkspace = () => useContext(WorkspaceContext)
export const WorkspaceProvider = ({ children }: { children: ReactNode }) => {
const { t } = useTranslation()

View File

@@ -83,6 +83,93 @@
line-height: 1.75rem !important;
}
}
@keyframes landing-rise {
from {
opacity: 0;
transform: translateY(28px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes landing-float {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-14px);
}
}
@keyframes landing-grid {
from {
transform: translate3d(0, 0, 0);
}
to {
transform: translate3d(0, 36px, 0);
}
}
@keyframes landing-aurora {
0%,
100% {
opacity: 0.8;
transform: translate3d(0, 0, 0) scale(1);
}
50% {
opacity: 1;
transform: translate3d(0, -1%, 0) scale(1.04);
}
}
@keyframes landing-shimmer {
0% {
background-position: 0% 50%;
}
100% {
background-position: 100% 50%;
}
}
.animate-landing-rise {
animation: landing-rise 0.9s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.animate-landing-float {
animation: landing-float 6s ease-in-out infinite;
}
.landing-hero-grid {
background-image:
linear-gradient(to right, rgba(15, 23, 42, 0.06) 1px, transparent 1px),
linear-gradient(to bottom, rgba(15, 23, 42, 0.06) 1px, transparent 1px);
background-size: 72px 72px;
mask-image: radial-gradient(circle at top, rgba(0, 0, 0, 0.95), transparent 78%);
animation: landing-grid 16s linear infinite;
}
.dark .landing-hero-grid {
background-image:
linear-gradient(to right, rgba(148, 163, 184, 0.09) 1px, transparent 1px),
linear-gradient(to bottom, rgba(148, 163, 184, 0.09) 1px, transparent 1px);
}
.landing-aurora {
background:
radial-gradient(circle at 10% 10%, rgba(34, 211, 238, 0.18), transparent 34%),
radial-gradient(circle at 85% 18%, rgba(245, 158, 11, 0.18), transparent 28%),
radial-gradient(circle at 58% 34%, rgba(20, 184, 166, 0.12), transparent 30%);
animation: landing-aurora 14s ease-in-out infinite;
}
.landing-shimmer {
background-size: 200% 200%;
animation: landing-shimmer 7s linear infinite;
}
}
@@ -120,6 +207,10 @@
scrollbar-color: #cbd5e1 transparent;
}
html {
scroll-behavior: smooth;
}
.dark * {
scrollbar-color: #334155 transparent;
}

View File

@@ -291,20 +291,108 @@ export const en = {
next: "Next",
},
sidebar: {
timesheet: "Timesheet",
reports: "Reports",
logs: "Logs",
sidebar: {
timesheet: "Timesheet",
reports: "Reports",
logs: "Logs",
workspaces: 'Workspaces',
clients: 'Clients',
projects: "Projects",
tags: "Tags",
expand: 'Expand',
collapse: 'Collapse',
},
ordering: {
createdAtDesc: "Newest First",
collapse: 'Collapse',
},
landing: {
brandLabel: "Operating system for time",
eyebrow: "Built for high-discipline teams that need clean time intelligence",
nav: {
demo: "Product demo",
features: "Core capabilities",
workflow: "How it works",
},
actions: {
switchToEnglish: "English",
switchToPersian: "فارسی",
signIn: "Sign in",
openApp: "Open app",
openWorkspace: "Open workspace",
startNow: "Start tracking with control",
watchDemo: "See the product demo",
readTerms: "Read terms",
},
hero: {
titleTop: "Turn every working hour into a reliable operating signal.",
titleAccent: "Qlockify makes time visible, accountable, and billable.",
description:
"A focused workspace for modern teams that need fast time capture, trustworthy project tracking, structured reports, and a log trail that management can actually use.",
},
metrics: {
capture: "cleaner billable capture",
visibility: "faster reporting visibility",
decision: "from raw entries to management context",
},
trust: {
first: "Precise timers with manual control when needed",
second: "Workspace permissions, logs, and rate-aware reporting",
third: "Built for agencies, consultancies, product teams, and operators",
},
capabilities: {
time: {
title: "Capture work without friction",
description:
"Start a timer, adjust historical entries, and keep project and tag context attached to every hour without slowing the team down.",
},
reports: {
title: "Read the business in minutes",
description:
"See daily output, billable performance, project distribution, and exportable report packs without spreadsheet cleanup.",
},
control: {
title: "Keep operations explainable",
description:
"Track who changed what, keep workspace roles explicit, and give management a cleaner operational trail than ad hoc chat or manual files.",
},
},
demo: {
timerTag: "Live timer",
timerTitle: "Current execution window",
timerText: "Design system refinement synced to the correct project, tags, and billable rate.",
panelLabel: "Interactive product preview",
panelTitle: "One surface for tracking, reporting, and operational clarity",
runningCard: "Active entry",
currentTask: "Enterprise landing page rollout",
currentTaskMeta: "Project: Qlockify Marketing · Tags: Design, Review, Delivery",
billableLabel: "Live billable rate",
reportCard: "Daily report trend",
opsCard: "Operational health",
opsLabels: ["Coverage", "Team focus", "Billing readiness"],
logCard: "Recent workspace activity",
logItems: [
{ title: "Rate updated for product design", meta: "Owner action · 3 minutes ago" },
{ title: "Client-facing project moved to archived", meta: "Admin action · 18 minutes ago" },
{ title: "Historic tag preserved on edited entry", meta: "Member action · 41 minutes ago" },
],
outcomeTag: "Management result",
outcomeText: "Less ambiguity at month end, fewer missing billable hours, and faster operational reviews.",
},
workflowTag: "Operational workflow",
workflowTitle: "A tighter loop from raw effort to usable management data.",
workflowDescription:
"Qlockify is designed to keep the path short: capture accurately, structure context once, and reuse the result everywhere from timesheets to reports to workspace-level decisions.",
workflow: {
capture: "Capture time at the source with project, tags, and billing context attached immediately.",
structure: "Keep every workspace action, membership change, and rate update visible and reviewable.",
improve: "Review daily and monthly performance with reports that are ready to export or act on.",
},
finalCtaTag: "Ready for production teams",
finalCtaTitle: "If your team sells expertise or ships client work, your time system should look this serious.",
finalCtaDescription:
"Open the app, create a workspace, and see how fast your reporting discipline improves when the product stops leaking context.",
},
ordering: {
createdAtDesc: "Newest First",
createdAt: "Olders First",
updatedAtDesc: "Recently Updated",
name: "Name (A-Z)",

View File

@@ -300,7 +300,95 @@ export const fa = {
collapse: 'جمع کردن',
},
ordering: {
landing: {
brandLabel: "زیرساخت عملیاتی زمان",
eyebrow: "طراحی‌شده برای تیم‌های دقیق که به داده زمانی قابل اتکا نیاز دارند",
nav: {
demo: "دموی محصول",
features: "قابلیت‌ها",
workflow: "فرآیند کار",
},
actions: {
switchToEnglish: "English",
switchToPersian: "فارسی",
signIn: "ورود",
openApp: "ورود به اپ",
openWorkspace: "باز کردن ورک‌اسپیس",
startNow: "شروع با کنترل کامل",
watchDemo: "مشاهده دموی محصول",
readTerms: "مطالعه قوانین",
},
hero: {
titleTop: "هر ساعت کاری را به یک سیگنال عملیاتی قابل اعتماد تبدیل کنید.",
titleAccent: "Qlockify زمان را شفاف، پاسخ‌گو و قابل‌صورتحساب می‌کند.",
description:
"یک محیط متمرکز برای تیم‌های مدرن که به ثبت سریع زمان، رهگیری دقیق پروژه، گزارش‌های قابل اتکا و لاگ عملیاتی واقعی برای مدیریت نیاز دارند.",
},
metrics: {
capture: "ثبت تمیزتر ساعات قابل‌صورتحساب",
visibility: "دسترسی سریع‌تر به دید گزارش‌دهی",
decision: "از ورودی خام تا تصمیم مدیریتی",
},
trust: {
first: "تایمر دقیق با امکان ویرایش دستی در زمان لازم",
second: "دسترسی‌ها، لاگ‌ها و گزارش‌های مبتنی بر نرخ",
third: "مناسب آژانس‌ها، شرکت‌های مشاوره، تیم‌های محصول و عملیات",
},
capabilities: {
time: {
title: "ثبت کار بدون اصطکاک",
description:
"تایمر را شروع کنید، ورودی‌های گذشته را اصلاح کنید و پروژه و تگ را بدون ایجاد اصطکاک برای تیم به هر ساعت متصل نگه دارید.",
},
reports: {
title: "کسب‌وکار را در چند دقیقه بخوانید",
description:
"خروجی روزانه، عملکرد قابل‌صورتحساب، توزیع پروژه‌ها و بسته‌های گزارشی قابل خروجی را بدون پاک‌سازی دستی فایل‌ها ببینید.",
},
control: {
title: "عملیات را قابل توضیح نگه دارید",
description:
"ببینید چه کسی چه چیزی را تغییر داده، نقش‌ها را شفاف نگه دارید و برای مدیریت یک رد عملیاتی تمیزتر از چت و فایل دستی بسازید.",
},
},
demo: {
timerTag: "تایمر زنده",
timerTitle: "بازه اجرای فعلی",
timerText: "بهبود دیزاین سیستم، متصل به پروژه درست، تگ‌های صحیح و نرخ قابل‌صورتحساب.",
panelLabel: "پیش‌نمایش تعاملی محصول",
panelTitle: "یک سطح واحد برای رهگیری، گزارش‌دهی و شفافیت عملیاتی",
runningCard: "ورودی فعال",
currentTask: "پیاده‌سازی لندینگ سازمانی",
currentTaskMeta: "پروژه: بازاریابی Qlockify · تگ‌ها: طراحی، بازبینی، تحویل",
billableLabel: "نرخ زنده قابل‌صورتحساب",
reportCard: "روند گزارش روزانه",
opsCard: "سلامت عملیات",
opsLabels: ["پوشش", "تمرکز تیم", "آمادگی صورتحساب"],
logCard: "آخرین فعالیت‌های ورک‌اسپیس",
logItems: [
{ title: "نرخ تیم طراحی محصول به‌روزرسانی شد", meta: "اقدام مالک · ۳ دقیقه پیش" },
{ title: "پروژه مشتری‌محور بایگانی شد", meta: "اقدام ادمین · ۱۸ دقیقه پیش" },
{ title: "تگ تاریخی روی ورودی ویرایش‌شده حفظ شد", meta: "اقدام عضو · ۴۱ دقیقه پیش" },
],
outcomeTag: "خروجی مدیریتی",
outcomeText: "ابهام کمتر در پایان ماه، ساعات قابل‌صورتحساب از‌دست‌رفته کمتر و بازبینی عملیاتی سریع‌تر.",
},
workflowTag: "فرآیند عملیاتی",
workflowTitle: "از تلاش خام تا داده مدیریتی قابل استفاده، با یک حلقه کوتاه‌تر.",
workflowDescription:
"Qlockify مسیر را کوتاه نگه می‌دارد: دقیق ثبت کنید، یک‌بار بستر را درست بسازید و همان نتیجه را در تایم‌شیت، گزارش و تصمیم‌گیری مدیریتی مصرف کنید.",
workflow: {
capture: "زمان را در مبدأ، همراه با پروژه، تگ و بستر مالی ثبت کنید.",
structure: "هر تغییر در اعضا، نرخ‌ها و تنظیمات ورک‌اسپیس را قابل مشاهده و قابل بررسی نگه دارید.",
improve: "عملکرد روزانه و ماهانه را با گزارش‌هایی بخوانید که آماده خروجی و اقدام هستند.",
},
finalCtaTag: "آماده برای تیم‌های جدی",
finalCtaTitle: "اگر تیم شما تخصص می‌فروشد یا پروژه مشتری تحویل می‌دهد، سیستم زمان شما هم باید همین‌قدر جدی باشد.",
finalCtaDescription:
"اپ را باز کنید، ورک‌اسپیس بسازید و ببینید وقتی محصول نشت بستر را متوقف می‌کند، انضباط گزارش‌دهی چقدر سریع بهتر می‌شود.",
},
ordering: {
createdAtDesc: "جدیدترین",
createdAt: "قدیمی‌ترین",
updatedAtDesc: "اخیراً بروزرسانی شده",

418
src/pages/Landing.tsx Normal file
View File

@@ -0,0 +1,418 @@
import { useMemo } from "react"
import { Link, useNavigate } from "react-router-dom"
import {
ArrowRight,
BarChart3,
CheckCircle2,
Clock3,
Command,
Globe2,
Layers3,
Moon,
ShieldCheck,
Sparkles,
Sun,
TimerReset,
Waypoints,
} from "lucide-react"
import { Button } from "../components/ui/button"
import { useTheme } from "../components/ThemeProvider"
import { useTranslation } from "../hooks/useTranslation"
import { cn } from "../lib/utils"
const formatNumber = (value: number, lang: "en" | "fa") =>
new Intl.NumberFormat(lang === "fa" ? "fa-IR" : "en-US").format(value)
export default function Landing() {
const navigate = useNavigate()
const { t, lang, setLanguage } = useTranslation()
const { theme, setTheme } = useTheme()
const isAuthenticated = typeof window !== "undefined" && !!localStorage.getItem("accessToken")
const isDarkMode =
theme === "dark" ||
(theme === "system" && document.documentElement.classList.contains("dark"))
const metrics = useMemo(
() => [
{
value: lang === "fa" ? "۹۸٪" : "98%",
label: t.landing.metrics.capture,
tone: "from-cyan-500/20 to-cyan-500/5 text-cyan-700 dark:text-cyan-200",
},
{
value: lang === "fa" ? "۴.۶×" : "4.6x",
label: t.landing.metrics.visibility,
tone: "from-emerald-500/20 to-emerald-500/5 text-emerald-700 dark:text-emerald-200",
},
{
value: lang === "fa" ? "< ۲m" : "< 2m",
label: t.landing.metrics.decision,
tone: "from-amber-500/20 to-amber-500/5 text-amber-700 dark:text-amber-200",
},
],
[lang, t.landing.metrics],
)
const capabilityCards = useMemo(
() => [
{
icon: TimerReset,
title: t.landing.capabilities.time.title,
description: t.landing.capabilities.time.description,
},
{
icon: BarChart3,
title: t.landing.capabilities.reports.title,
description: t.landing.capabilities.reports.description,
},
{
icon: ShieldCheck,
title: t.landing.capabilities.control.title,
description: t.landing.capabilities.control.description,
},
],
[t.landing.capabilities],
)
const workflow = useMemo(
() => [
t.landing.workflow.capture,
t.landing.workflow.structure,
t.landing.workflow.improve,
],
[t.landing.workflow],
)
const ctaTarget = isAuthenticated ? "/timesheet" : "/auth"
return (
<div className="scroll-smooth min-h-screen overflow-x-hidden bg-[radial-gradient(circle_at_top,#e0f2fe_0%,#f8fafc_36%,#eef2ff_100%)] text-slate-950 dark:bg-[radial-gradient(circle_at_top,#082f49_0%,#020617_40%,#020617_100%)] dark:text-slate-50">
<div className="landing-aurora pointer-events-none fixed inset-0 opacity-80" />
<div className="landing-hero-grid pointer-events-none fixed inset-0 opacity-70 dark:opacity-40" />
<div className="pointer-events-none fixed left-[-12rem] top-24 h-80 w-80 rounded-full bg-cyan-400/20 blur-3xl dark:bg-cyan-500/10" />
<div className="pointer-events-none fixed right-[-10rem] top-44 h-72 w-72 rounded-full bg-amber-300/25 blur-3xl dark:bg-amber-400/10" />
<div className="relative mx-auto flex min-h-screen max-w-7xl flex-col px-4 pb-14 pt-5 sm:px-6 lg:px-8">
<header className="animate-landing-rise flex items-center justify-between rounded-full border border-white/70 bg-white/75 px-4 py-3 shadow-[0_20px_60px_-36px_rgba(15,23,42,0.45)] backdrop-blur-xl dark:border-white/10 dark:bg-slate-950/55">
<button
type="button"
onClick={() => navigate("/")}
className="inline-flex items-center gap-3 rounded-full px-2 py-1 text-left"
>
<span className="flex h-10 w-10 items-center justify-center rounded-2xl bg-slate-950 text-white shadow-lg shadow-cyan-500/20 dark:bg-white dark:text-slate-950">
<Command className="h-5 w-5" />
</span>
<span>
<span className="block text-lg font-semibold">{t.title}</span>
</span>
</button>
<div className="hidden items-center gap-2 md:flex">
<a href="#demo" className="rounded-full px-4 py-2 text-sm font-medium text-slate-600 transition hover:bg-slate-100 hover:text-slate-950 dark:text-slate-300 dark:hover:bg-slate-900 dark:hover:text-white">
{t.landing.nav.demo}
</a>
<a href="#features" className="rounded-full px-4 py-2 text-sm font-medium text-slate-600 transition hover:bg-slate-100 hover:text-slate-950 dark:text-slate-300 dark:hover:bg-slate-900 dark:hover:text-white">
{t.landing.nav.features}
</a>
<a href="#workflow" className="rounded-full px-4 py-2 text-sm font-medium text-slate-600 transition hover:bg-slate-100 hover:text-slate-950 dark:text-slate-300 dark:hover:bg-slate-900 dark:hover:text-white">
{t.landing.nav.workflow}
</a>
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => setLanguage(lang === "fa" ? "en" : "fa")}
className="inline-flex h-11 items-center gap-2 rounded-full border border-slate-200/80 bg-white/80 px-4 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-800 dark:bg-slate-950/80 dark:text-slate-200 dark:hover:bg-slate-900"
>
<Globe2 className="h-4 w-4" />
{lang === "fa" ? t.landing.actions.switchToEnglish : t.landing.actions.switchToPersian}
</button>
<button
type="button"
onClick={() => setTheme(isDarkMode ? "light" : "dark")}
className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-slate-200/80 bg-white/80 text-slate-700 transition hover:bg-slate-50 dark:border-slate-800 dark:bg-slate-950/80 dark:text-slate-200 dark:hover:bg-slate-900"
aria-label={isDarkMode ? t.lightMode : t.darkMode}
>
{isDarkMode ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
</button>
</div>
</header>
<section className="relative grid flex-1 items-center gap-10 py-12 lg:grid-cols-[1.08fr_0.92fr] lg:py-20">
<div className="space-y-8">
<div className="animate-landing-rise [animation-delay:120ms]">
<div className="mb-5 inline-flex items-center gap-2 rounded-full border border-cyan-200/70 bg-white/75 px-4 py-2 text-sm font-medium text-cyan-900 shadow-sm backdrop-blur dark:border-cyan-500/20 dark:bg-cyan-500/10 dark:text-cyan-100">
<Sparkles className="h-4 w-4" />
{t.landing.eyebrow}
</div>
<h1 className="max-w-4xl text-5xl font-semibold leading-[1.1] tracking-[-0.06em] text-slate-950 sm:text-5xl lg:text-6xl dark:text-white">
{t.landing.hero.titleTop}
<span className="mt-4 pb-4 landing-shimmer block bg-[linear-gradient(120deg,#0f172a_15%,#0891b2_48%,#0f766e_78%,#0f172a_100%)] bg-clip-text text-transparent dark:bg-[linear-gradient(120deg,#ffffff_18%,#67e8f9_48%,#2dd4bf_78%,#ffffff_100%)]">
{t.landing.hero.titleAccent}
</span>
</h1>
<p className="mt-6 max-w-2xl text-lg leading-8 text-slate-600 dark:text-slate-300 sm:text-xl">
{t.landing.hero.description}
</p>
</div>
<div className="animate-landing-rise flex flex-col gap-3 sm:flex-row [animation-delay:220ms]">
<Button
onClick={() => navigate(ctaTarget)}
className="h-14 rounded-full bg-slate-950 px-7 text-base text-white shadow-[0_30px_80px_-28px_rgba(8,145,178,0.45)] hover:bg-slate-800 dark:bg-cyan-400 dark:text-slate-950 dark:hover:bg-cyan-300"
>
{isAuthenticated ? t.landing.actions.openWorkspace : t.landing.actions.startNow}
<ArrowRight className={cn("ms-2 h-4 w-4", lang === "fa" && "rtl:rotate-180")} />
</Button>
<a
href="#demo"
className="inline-flex h-14 items-center justify-center rounded-full border border-slate-200 bg-white/85 px-7 text-base font-medium text-slate-800 shadow-sm backdrop-blur transition hover:bg-white dark:border-slate-800 dark:bg-slate-950/70 dark:text-slate-100 dark:hover:bg-slate-900"
>
{t.landing.actions.watchDemo}
</a>
</div>
<div className="animate-landing-rise grid gap-3 sm:grid-cols-3 [animation-delay:320ms]">
{metrics.map((metric) => (
<div
key={metric.label}
className={cn(
"rounded-3xl border border-white/70 bg-gradient-to-br p-5 shadow-[0_28px_70px_-40px_rgba(15,23,42,0.55)] backdrop-blur-lg dark:border-white/10 dark:bg-slate-950/60",
metric.tone,
)}
>
<div className="text-3xl font-semibold tracking-[-0.04em]">{metric.value}</div>
<div className="mt-2 text-sm font-medium text-slate-600 dark:text-slate-300">{metric.label}</div>
</div>
))}
</div>
<div className="animate-landing-rise flex flex-wrap items-center gap-4 text-sm text-slate-500 dark:text-slate-400 [animation-delay:420ms]">
<span className="inline-flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-emerald-500" />
{t.landing.trust.first}
</span>
<span className="inline-flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-emerald-500" />
{t.landing.trust.second}
</span>
<span className="inline-flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-emerald-500" />
{t.landing.trust.third}
</span>
</div>
</div>
<div id="demo" className="relative animate-landing-rise [animation-delay:180ms]">
<div className="animate-landing-float absolute -left-6 top-10 hidden rounded-3xl border border-white/60 bg-white/80 p-4 shadow-[0_24px_70px_-34px_rgba(8,145,178,0.6)] backdrop-blur-xl lg:block dark:border-white/10 dark:bg-slate-950/70">
<div className="mb-3 flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-cyan-500/15 text-cyan-700 dark:text-cyan-200">
<Clock3 className="h-5 w-5" />
</div>
<div>
<div className="text-xs uppercase tracking-[0.18em] text-slate-400">{t.landing.demo.timerTag}</div>
<div className="text-sm font-semibold text-slate-900 dark:text-white">{t.landing.demo.timerTitle}</div>
</div>
</div>
<div className="text-3xl font-semibold tracking-[-0.05em] text-slate-950 dark:text-white">
{lang === "fa" ? "۰۲:۴۶:۱۸" : "02:46:18"}
</div>
<div className="mt-2 text-sm text-slate-500 dark:text-slate-400">{t.landing.demo.timerText}</div>
</div>
<div className="relative overflow-hidden rounded-[2rem] border border-white/70 bg-white/80 p-4 shadow-[0_45px_110px_-48px_rgba(15,23,42,0.6)] backdrop-blur-2xl dark:border-white/10 dark:bg-slate-950/70 sm:p-6">
<div className="mb-5 flex items-center justify-between">
<div>
<div className="text-xs uppercase tracking-[0.22em] text-slate-400 dark:text-slate-500">
{t.landing.demo.panelLabel}
</div>
<div className="mt-2 text-2xl font-semibold tracking-[-0.04em] text-slate-950 dark:text-white">
{t.landing.demo.panelTitle}
</div>
</div>
<div className="rounded-full border border-emerald-200 bg-emerald-50 px-4 py-2 text-sm font-semibold text-emerald-700 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-200">
{lang === "fa" ? "زنده" : "Live"}
</div>
</div>
<div className="grid gap-4 lg:grid-cols-[1.15fr_0.85fr]">
<div className="space-y-4">
<div className="rounded-[1.5rem] border border-slate-200/80 bg-slate-950 p-5 text-white shadow-inner dark:border-slate-800">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<div className="text-xs uppercase tracking-[0.2em] text-cyan-200/70">{t.landing.demo.runningCard}</div>
<div className="mt-2 text-xl font-semibold">{t.landing.demo.currentTask}</div>
<div className="mt-2 text-sm text-slate-300">{t.landing.demo.currentTaskMeta}</div>
</div>
<div className="rounded-3xl bg-white/10 px-4 py-3 text-right backdrop-blur">
<div className="text-xs uppercase tracking-[0.18em] text-slate-300">{t.landing.demo.billableLabel}</div>
<div className="mt-2 text-2xl font-semibold">{lang === "fa" ? "۹۵ دلار" : "$95"}</div>
</div>
</div>
<div className="mt-5 h-2 overflow-hidden rounded-full bg-white/10">
<div className="h-full w-[78%] rounded-full bg-[linear-gradient(90deg,#67e8f9,#14b8a6)]" />
</div>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div className="rounded-[1.5rem] border border-slate-200/80 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900">
<div className="flex items-center gap-2 text-sm font-semibold text-slate-900 dark:text-white">
<BarChart3 className="h-4 w-4 text-cyan-500" />
{t.landing.demo.reportCard}
</div>
<div className="mt-4 flex h-32 items-end gap-2">
{[34, 56, 48, 72, 65, 88, 76].map((height, index) => (
<div
key={height}
className="flex-1 rounded-t-2xl bg-[linear-gradient(180deg,#67e8f9_0%,#0f766e_100%)] opacity-90"
style={{
height: `${height}%`,
animationDelay: `${index * 120}ms`,
}}
/>
))}
</div>
</div>
<div className="rounded-[1.5rem] border border-slate-200/80 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900">
<div className="flex items-center gap-2 text-sm font-semibold text-slate-900 dark:text-white">
<Waypoints className="h-4 w-4 text-amber-500" />
{t.landing.demo.opsCard}
</div>
<div className="mt-4 space-y-3">
{[82, 63, 91].map((value, index) => (
<div key={value}>
<div className="mb-2 flex items-center justify-between text-xs text-slate-500 dark:text-slate-400">
<span>{t.landing.demo.opsLabels[index]}</span>
<span>{formatNumber(value, lang)}%</span>
</div>
<div className="h-2 rounded-full bg-slate-100 dark:bg-slate-800">
<div
className="h-full rounded-full bg-[linear-gradient(90deg,#f59e0b,#f97316)]"
style={{ width: `${value}%` }}
/>
</div>
</div>
))}
</div>
</div>
</div>
</div>
<div className="space-y-4">
<div className="rounded-[1.5rem] border border-slate-200/80 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900">
<div className="flex items-center gap-2 text-sm font-semibold text-slate-900 dark:text-white">
<Layers3 className="h-4 w-4 text-violet-500" />
{t.landing.demo.logCard}
</div>
<div className="mt-4 space-y-3">
{t.landing.demo.logItems.map((item: { title: string; meta: string }, index: number) => (
<div key={item.title} className="rounded-2xl border border-slate-200 bg-slate-50 p-3 dark:border-slate-800 dark:bg-slate-950">
<div className="flex items-start justify-between gap-3">
<div>
<div className="text-sm font-medium text-slate-900 dark:text-white">{item.title}</div>
<div className="mt-1 text-xs text-slate-500 dark:text-slate-400">{item.meta}</div>
</div>
<div className={cn(
"mt-0.5 h-2.5 w-2.5 rounded-full",
index === 0 ? "bg-emerald-500" : index === 1 ? "bg-cyan-500" : "bg-amber-500",
)} />
</div>
</div>
))}
</div>
</div>
<div className="rounded-[1.5rem] border border-slate-200/80 bg-slate-950 p-4 text-white shadow-inner dark:border-slate-800">
<div className="text-xs uppercase tracking-[0.18em] text-slate-400">{t.landing.demo.outcomeTag}</div>
<div className="mt-3 text-4xl font-semibold tracking-[-0.05em]">{lang === "fa" ? "۳۶٪" : "36%"}</div>
<div className="mt-2 text-sm text-slate-300">{t.landing.demo.outcomeText}</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="features" className="grid gap-4 py-8 md:grid-cols-3">
{capabilityCards.map(({ icon: Icon, title, description }, index) => (
<div
key={title}
className="animate-landing-rise rounded-[2rem] border border-white/70 bg-white/80 p-6 shadow-[0_30px_80px_-50px_rgba(15,23,42,0.6)] backdrop-blur-xl dark:border-white/10 dark:bg-slate-950/65"
style={{ animationDelay: `${index * 120}ms` }}
>
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-slate-950 text-white dark:bg-cyan-400 dark:text-slate-950">
<Icon className="h-5 w-5" />
</div>
<h2 className="mt-5 text-2xl font-semibold tracking-[-0.04em] text-slate-950 dark:text-white">{title}</h2>
<p className="mt-3 text-base leading-7 text-slate-600 dark:text-slate-300">{description}</p>
</div>
))}
</section>
<section id="workflow" className="grid gap-6 py-8 lg:grid-cols-[0.9fr_1.1fr]">
<div className="rounded-[2rem] border border-white/70 bg-white/80 p-7 shadow-[0_30px_80px_-50px_rgba(15,23,42,0.6)] backdrop-blur-xl dark:border-white/10 dark:bg-slate-950/65">
<div className="text-sm font-semibold uppercase tracking-[0.2em] text-cyan-700 dark:text-cyan-300">
{t.landing.workflowTag}
</div>
<h2 className="mt-4 text-4xl font-semibold tracking-[-0.05em] text-slate-950 dark:text-white">
{t.landing.workflowTitle}
</h2>
<p className="mt-4 max-w-xl text-lg leading-8 text-slate-600 dark:text-slate-300">
{t.landing.workflowDescription}
</p>
</div>
<div className="grid gap-4 md:grid-cols-3">
{workflow.map((item, index) => (
<div
key={item}
className="rounded-[2rem] border border-white/70 bg-gradient-to-br from-white/95 to-slate-50/80 p-6 shadow-[0_26px_70px_-48px_rgba(15,23,42,0.65)] backdrop-blur-xl dark:border-white/10 dark:from-slate-950/80 dark:to-slate-900/55"
>
<div className="text-sm font-semibold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-500">
{lang === "fa" ? `گام ${formatNumber(index + 1, lang)}` : `Step ${index + 1}`}
</div>
<div className="mt-12 text-xl font-semibold leading-8 tracking-[-0.03em] text-slate-950 dark:text-white">
{item}
</div>
</div>
))}
</div>
</section>
<section className="py-8">
<div className="relative overflow-hidden rounded-[2.5rem] border border-slate-950/5 bg-slate-950 px-6 py-10 text-white shadow-[0_40px_100px_-40px_rgba(15,23,42,0.8)] dark:border-white/10 sm:px-10">
<div className="pointer-events-none absolute inset-y-0 right-0 w-[45%] bg-[radial-gradient(circle_at_top_right,rgba(34,211,238,0.35),transparent_55%),radial-gradient(circle_at_bottom_right,rgba(245,158,11,0.22),transparent_45%)]" />
<div className="relative flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
<div className="max-w-3xl">
<div className="text-sm font-semibold uppercase tracking-[0.2em] text-cyan-300">{t.landing.finalCtaTag}</div>
<h2 className="mt-4 text-4xl font-semibold leading-[1.1] tracking-[-0.05em] sm:text-5xl">
{t.landing.finalCtaTitle}
</h2>
<p className="mt-4 text-lg leading-8 text-slate-300">{t.landing.finalCtaDescription}</p>
</div>
<div className="flex flex-col gap-3 sm:flex-row">
<Button
onClick={() => navigate(ctaTarget)}
className="h-14 rounded-full bg-white px-7 text-base font-semibold text-slate-950 hover:bg-slate-100"
>
{isAuthenticated ? t.landing.actions.openWorkspace : t.landing.actions.startNow}
</Button>
<Button
variant="outline"
asChild
className="h-14 rounded-full border-white/20 bg-white/5 px-7 text-base text-white hover:bg-white/10 dark:border-white/20 dark:bg-white/5 dark:text-white dark:hover:bg-white/10"
>
<Link to="/terms">{t.landing.actions.readTerms}</Link>
</Button>
</div>
</div>
</div>
</section>
</div>
</div>
)
}