Files
ar.badieiandCursor c2376b3b9e
Build & Push Docker Image / build (push) Has been cancelled
feat: redesign landing page with brand system and section blocks
Refresh the homepage around new hero/services/roadmap sections, shared design tokens, and updated brand assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 17:00:55 +03:30

712 lines
26 KiB
TypeScript

"use client";
import { motion, useReducedMotion } from "motion/react";
import { Check, ChevronLeft, LayoutTemplate, Lock } from "lucide-react";
import Link from "next/link";
import { Container } from "@/components/container";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { blockEase } from "@/lib/block-motion";
import { layout } from "@/lib/layout";
import { type } from "@/lib/typography";
import { cn } from "@/lib/utils";
/** Soft page-load entrance — opacity + gentle rise. */
const heroFadeEase = blockEase;
const heroContainerVariants = {
hidden: {},
visible: {
transition: {
staggerChildren: 0.14,
delayChildren: 0.06,
},
},
};
const heroColumnVariants = {
hidden: {},
visible: {
transition: {
staggerChildren: 0.09,
delayChildren: 0.04,
},
},
};
const heroItemVariants = {
hidden: { opacity: 0, y: 14 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.7, ease: heroFadeEase },
},
};
const heroMockupVariants = {
hidden: { opacity: 0, y: 20, scale: 0.985 },
visible: {
opacity: 1,
y: 0,
scale: 1,
transition: { duration: 0.9, ease: heroFadeEase },
},
};
/** Very soft float — long cycle, tiny travel, custom ease. */
const floatTransition = {
duration: 11,
repeat: Infinity,
ease: [0.45, 0.05, 0.55, 0.95] as const,
};
const trustAvatars = [
{ initials: "سا", tone: "bg-primary/15 text-primary" },
{ initials: "ام", tone: "bg-muted text-foreground" },
{ initials: "نر", tone: "bg-primary/10 text-primary" },
{ initials: "مد", tone: "bg-muted text-muted-foreground" },
] as const;
const screenBlocks = [
{ label: "طراحی", w: "w-[42%]" },
{ label: "سئو", w: "w-[34%]" },
{ label: "پشتیبانی", w: "w-[48%]" },
] as const;
/** Soft ease for in-screen design loop. */
const screenLoopEase = [0.45, 0.05, 0.55, 0.95] as const;
/** Extra-soft ease for skeleton text lines — slow in/out, no snap. */
const screenLineEase = [0.37, 0, 0.2, 1] as const;
const screenLineTransition = {
duration: 9.5,
repeat: Infinity,
ease: screenLineEase,
times: [0, 0.35, 0.65, 1] as const,
};
interface HeroReuiProps {
className?: string;
}
export function HeroReui({ className }: HeroReuiProps) {
const reduceMotion = useReducedMotion();
return (
<section
className={cn(
"section-shell hero-atmosphere flex min-h-svh flex-col justify-center",
/* Clear fixed header; bottom matches sectionY so gap to next block is standard */
layout.sectionTopWithHeader,
className
)}
>
<span className="hero-square-pattern" aria-hidden />
<Container className="section-content w-full">
<motion.div
className="grid items-center gap-10 sm:gap-12 lg:grid-cols-[minmax(0,0.92fr)_minmax(0,1.08fr)] lg:gap-16 xl:gap-20"
initial={reduceMotion ? false : "hidden"}
animate="visible"
variants={reduceMotion ? undefined : heroContainerVariants}
>
{/* Copy */}
<motion.div
className="mx-auto flex max-w-xl flex-col items-center text-center lg:mx-0 lg:items-start lg:text-start"
variants={reduceMotion ? undefined : heroColumnVariants}
>
<motion.div variants={reduceMotion ? undefined : heroItemVariants}>
<Link
href="#services"
className={cn(
type["body-sm"],
"group inline-flex max-w-full items-center gap-1.5 rounded-full border border-surface bg-background/70 px-2.5 py-1.5 text-muted-foreground shadow-surface-sm backdrop-blur-sm transition-colors duration-200 hover:border-primary/35 hover:text-foreground sm:gap-2 sm:px-3"
)}
>
<Badge variant="default" className={cn(type.micro, "shrink-0 px-1.5 py-0")}>
اصفهان
</Badge>
<span className="truncate">طراحی سایت و سئو حرفه‌ای</span>
<ChevronLeft className="size-3.5 shrink-0 opacity-60 transition-transform duration-200 group-hover:-translate-x-0.5 rtl:group-hover:translate-x-0.5" />
</Link>
</motion.div>
<motion.h1
variants={reduceMotion ? undefined : heroItemVariants}
className={cn(
type.display,
"mt-6 text-center text-foreground lg:text-start"
)}
>
امین رایان
</motion.h1>
<motion.p
variants={reduceMotion ? undefined : heroItemVariants}
className={cn(
type["body-lg"],
"mt-3 max-w-md text-center font-medium text-foreground/90 lg:text-start"
)}
>
امینِ رویای آنلاین
</motion.p>
<motion.p
variants={reduceMotion ? undefined : heroItemVariants}
className={cn(
type.body,
"mt-4 max-w-md text-center text-muted-foreground lg:text-start"
)}
>
از طراحی وب‌سایت حرفه‌ای تا بهینه‌سازی موتورهای جستجو و پشتیبانی
پایدار؛ همراه مطمئن کسب‌وکارهای اصفهان و سراسر ایران.
</motion.p>
<motion.div
variants={reduceMotion ? undefined : heroItemVariants}
className="mt-8 flex w-full flex-col gap-3 sm:w-auto sm:flex-row sm:items-center"
>
<Button size="lg" className="w-full sm:w-auto" asChild>
<a href="#contact">
درخواست مشاوره
<ChevronLeft className="size-4" />
</a>
</Button>
<Button size="lg" variant="secondary" className="w-full sm:w-auto" asChild>
<a href="#services">مشاهده خدمات</a>
</Button>
</motion.div>
<motion.div
variants={reduceMotion ? undefined : heroItemVariants}
className="mt-10 flex items-center gap-3"
>
<div className="flex -space-x-2 space-x-reverse" aria-hidden>
{trustAvatars.map((avatar) => (
<span
key={avatar.initials}
className={cn(
type.micro,
"flex size-8 items-center justify-center rounded-full border-2 border-background font-semibold",
avatar.tone
)}
>
{avatar.initials}
</span>
))}
</div>
<p className={cn(type.caption, "max-w-[15rem]")}>
مورد اعتماد کسب‌وکارهایی که رشد آنلاین می‌خواهند
</p>
</motion.div>
</motion.div>
{/* Desk + monitor scene */}
<motion.div
className="relative mx-auto w-full max-w-xl lg:max-w-none"
variants={reduceMotion ? undefined : heroMockupVariants}
>
<div className="relative">
<DeskMonitorMockup reduceMotion={!!reduceMotion} />
{/* Soft status chip — kept minimal beside the desk */}
<motion.div
className="absolute start-1 top-[10%] z-20 max-w-[calc(100%-1rem)] will-change-transform sm:start-0 sm:max-w-none lg:-start-4"
initial={reduceMotion ? false : { opacity: 0, y: 10 }}
animate={
reduceMotion
? undefined
: { opacity: 1, y: [0, -14, 0, -8, 0], x: [0, 4, 0, -3, 0] }
}
transition={
reduceMotion
? undefined
: {
opacity: {
duration: 0.65,
delay: 0.85,
ease: heroFadeEase,
},
y: {
...floatTransition,
duration: 7.5,
delay: 1.1,
},
x: {
...floatTransition,
duration: 9,
delay: 1.1,
},
}
}
>
<div className="flex items-center gap-2 rounded-xl border border-surface bg-background/95 px-2.5 py-1.5 shadow-floating backdrop-blur-sm sm:gap-2.5 sm:px-3 sm:py-2">
<span className="flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/15 text-primary sm:size-6">
<Check className="size-3 sm:size-3.5" strokeWidth={2.5} />
</span>
<div className="min-w-0">
<p className={cn(type.caption, "font-medium text-foreground")}>
در حال طراحی
</p>
<p className={cn(type.micro, "truncate text-muted-foreground")}>
لندینگ فروشگاهی
</p>
</div>
</div>
</motion.div>
<motion.div
className="absolute end-1 bottom-[22%] z-20 will-change-transform sm:end-0 lg:-end-3"
initial={reduceMotion ? false : { opacity: 0, y: 10 }}
animate={
reduceMotion
? undefined
: { opacity: 1, y: [0, 12, 0, 7, 0], x: [0, -4, 0, 3, 0] }
}
transition={
reduceMotion
? undefined
: {
opacity: {
duration: 0.65,
delay: 1,
ease: heroFadeEase,
},
y: {
...floatTransition,
duration: 8.5,
delay: 1.35,
},
x: {
...floatTransition,
duration: 10,
delay: 1.35,
},
}
}
>
<div className="flex items-center gap-1.5 rounded-full border border-surface bg-background/95 px-2 py-1.5 shadow-floating backdrop-blur-sm sm:gap-2 sm:px-2.5">
<span className="flex size-5 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground">
<LayoutTemplate className="size-3" />
</span>
<span className={cn(type.caption, "font-medium text-foreground")}>
پیش‌نمایش زنده
</span>
</div>
</motion.div>
</div>
</motion.div>
</motion.div>
</Container>
</section>
);
}
function DeskMonitorMockup({ reduceMotion }: { reduceMotion: boolean }) {
return (
<div
className="relative mx-auto w-full max-w-lg lg:max-w-none"
role="img"
aria-label="میز کار طراحی وب با مانیتور و پیش‌نمایش زنده صفحه"
>
{/* Soft desk glow behind monitor */}
<div
className="pointer-events-none absolute inset-x-[8%] top-[18%] bottom-[18%] rounded-[50%] bg-primary/10 blur-3xl dark:bg-primary/15"
aria-hidden
/>
<div className="relative flex flex-col items-center">
{/* Monitor + stand float above a fixed desk */}
<motion.div
className="relative flex w-full flex-col items-center will-change-transform"
animate={reduceMotion ? undefined : { y: [0, -14, 0] }}
transition={reduceMotion ? undefined : floatTransition}
>
<div className="relative w-full">
<div
className={cn(
"overflow-hidden rounded-[1.05rem] border border-foreground/15 bg-foreground/90 p-[0.55rem] shadow-hero sm:rounded-[1.25rem] sm:p-2.5",
"dark:border-white/10 dark:bg-zinc-900"
)}
>
{/* Camera notch */}
<div
className="mx-auto mb-1.5 h-1 w-10 rounded-full bg-background/20 sm:mb-2 sm:h-1.5 sm:w-12"
aria-hidden
/>
{/* Screen */}
<div className="relative overflow-hidden rounded-md bg-background sm:rounded-lg">
<div className="aspect-[16/10] sm:aspect-[16/10.5]">
<LiveWebsiteScreen reduceMotion={reduceMotion} />
</div>
</div>
</div>
</div>
{/* Neck */}
<div
className="h-5 w-14 bg-gradient-to-b from-foreground/55 to-foreground/35 sm:h-6 sm:w-16 dark:from-zinc-600 dark:to-zinc-700"
aria-hidden
/>
{/* Stand base */}
<div
className="h-2.5 w-28 rounded-t-md bg-gradient-to-b from-foreground/40 to-foreground/25 sm:h-3 sm:w-32 dark:from-zinc-600 dark:to-zinc-800"
aria-hidden
/>
<div
className="h-1.5 w-36 rounded-full bg-foreground/20 sm:w-40 dark:bg-zinc-700/80"
aria-hidden
/>
</motion.div>
{/* Desk surface stays grounded */}
<div className="relative mt-3 w-full max-w-md" aria-hidden>
<div className="h-3 rounded-full bg-gradient-to-b from-muted via-muted/80 to-transparent shadow-surface-sm" />
{/* Tiny desk props */}
<div className="absolute -top-1 end-[12%] hidden size-6 rounded-full border border-surface bg-primary/15 sm:block" />
<div className="absolute -top-0.5 start-[14%] hidden h-2 w-10 rounded-full bg-muted-foreground/20 sm:block" />
</div>
</div>
</div>
);
}
function LiveWebsiteScreen({ reduceMotion }: { reduceMotion: boolean }) {
return (
<div className="relative flex h-full flex-col bg-background">
{/* Browser chrome */}
<div className="flex shrink-0 items-center gap-2 border-b border-surface bg-muted/40 px-2 py-1.5 sm:px-2.5 sm:py-2">
<div className="flex shrink-0 gap-1" aria-hidden>
<span className="size-1.5 rounded-full bg-border sm:size-2" />
<span className="size-1.5 rounded-full bg-border sm:size-2" />
<span className="size-1.5 rounded-full bg-border sm:size-2" />
</div>
<div className="flex min-w-0 flex-1 items-center justify-center gap-1 rounded-md border border-surface bg-background px-2 py-0.5 sm:gap-1.5 sm:px-2.5 sm:py-1">
<Lock className="size-2.5 shrink-0 text-muted-foreground sm:size-3" />
<span className={cn(type.micro, "truncate text-muted-foreground")}>
preview.aminrayan.ir
</span>
{!reduceMotion && (
<motion.span
className="ms-0.5 size-1.5 shrink-0 rounded-full bg-primary"
aria-hidden
animate={{ opacity: [0.35, 1, 0.35] }}
transition={{
duration: 2.4,
repeat: Infinity,
ease: screenLoopEase,
}}
/>
)}
</div>
</div>
{/* Mini site canvas */}
<div className="relative min-h-0 flex-1 overflow-hidden p-2.5 sm:p-3.5">
{/* Soft page wash */}
<div
className="pointer-events-none absolute inset-0 bg-gradient-to-br from-primary/[0.04] via-transparent to-muted/40"
aria-hidden
/>
{/* Ambient light sweep across the canvas */}
{!reduceMotion && (
<motion.div
className="pointer-events-none absolute inset-y-0 w-1/3 bg-gradient-to-l from-transparent via-primary/[0.07] to-transparent"
aria-hidden
animate={{ x: ["120%", "-140%"] }}
transition={{
duration: 7.5,
repeat: Infinity,
ease: "linear",
repeatDelay: 1.2,
}}
/>
)}
<div className="relative flex h-full flex-col gap-2.5 sm:gap-3">
{/* Mini nav */}
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-1.5">
<motion.span
className="size-4 rounded-md bg-primary sm:size-5"
animate={
reduceMotion
? undefined
: { scale: [1, 1.08, 1], rotate: [0, -4, 0] }
}
transition={
reduceMotion
? undefined
: {
duration: 5,
repeat: Infinity,
ease: screenLoopEase,
}
}
/>
<span className={cn(type.micro, "font-semibold text-foreground")}>
برند شما
</span>
</div>
<div className="hidden items-center gap-1.5 sm:flex" aria-hidden>
{[0, 1, 2].map((i) => (
<span
key={i}
className={cn(
"relative h-1.5 overflow-hidden rounded-full bg-muted-foreground/25",
i === 2 ? "w-6" : "w-8"
)}
>
<motion.span
className="absolute inset-0 rounded-full bg-primary/70"
animate={
reduceMotion ? undefined : { opacity: [0, 1, 0] }
}
transition={
reduceMotion
? undefined
: {
duration: 3.8,
repeat: Infinity,
ease: screenLoopEase,
delay: i * 0.85,
}
}
/>
</span>
))}
</div>
</div>
{/* Hero copy block — soft breathe via scale (no layout width jumps) */}
<div className="mt-0.5 space-y-1.5 sm:mt-1 sm:space-y-2">
<motion.div
className="h-2.5 w-[72%] origin-right rounded-full bg-foreground/85 sm:h-3"
animate={
reduceMotion
? undefined
: {
scaleX: [0.86, 1, 0.94, 1],
opacity: [0.72, 1, 0.88, 1],
}
}
transition={
reduceMotion ? undefined : screenLineTransition
}
/>
<motion.div
className="h-1.5 w-[88%] origin-right rounded-full bg-muted-foreground/30 sm:h-2"
animate={
reduceMotion
? undefined
: {
scaleX: [0.84, 1, 0.92, 1],
opacity: [0.4, 0.8, 0.55, 0.8],
}
}
transition={
reduceMotion
? undefined
: { ...screenLineTransition, delay: 0.4 }
}
/>
<motion.div
className="h-1.5 w-[64%] origin-right rounded-full bg-muted-foreground/20 sm:h-2"
animate={
reduceMotion
? undefined
: {
scaleX: [0.82, 1, 0.9, 1],
opacity: [0.32, 0.65, 0.45, 0.65],
}
}
transition={
reduceMotion
? undefined
: { ...screenLineTransition, delay: 0.75 }
}
/>
</div>
{/* CTA — click target for cursor loop */}
<div className="relative mt-1 inline-flex sm:mt-1.5">
<motion.div
className="relative inline-flex items-center rounded-md bg-primary px-2.5 py-1 text-primary-foreground shadow-surface-sm sm:px-3 sm:py-1.5"
animate={
reduceMotion
? undefined
: {
scale: [1, 1, 1, 0.94, 1.06, 1, 1],
y: [0, -1, 0, 0, 0, 0, 0],
}
}
transition={
reduceMotion
? undefined
: {
duration: 10,
repeat: Infinity,
ease: screenLoopEase,
times: [0, 0.18, 0.32, 0.4, 0.48, 0.58, 1],
}
}
>
<span className={cn(type.micro, "font-medium")}>شروع پروژه</span>
<ChevronLeft className="ms-0.5 size-2.5 opacity-80 sm:size-3" />
</motion.div>
{/* Click ripple */}
{!reduceMotion && (
<motion.span
className="pointer-events-none absolute start-1/2 top-1/2 size-8 -translate-x-1/2 -translate-y-1/2 rounded-full border border-primary/50"
aria-hidden
animate={{
opacity: [0, 0, 0, 0.6, 0, 0, 0],
scale: [0.4, 0.4, 0.4, 1.4, 1.8, 1.8, 1.8],
}}
transition={{
duration: 10,
repeat: Infinity,
ease: screenLoopEase,
times: [0, 0.32, 0.38, 0.46, 0.58, 0.7, 1],
}}
/>
)}
</div>
{/* Soft progress / build bar */}
<div className="h-1 overflow-hidden rounded-full bg-muted/60" aria-hidden>
{!reduceMotion ? (
<motion.div
className="h-full rounded-full bg-primary/70"
animate={{ width: ["12%", "78%", "52%", "92%", "12%"] }}
transition={{
duration: 10,
repeat: Infinity,
ease: screenLoopEase,
}}
/>
) : (
<div className="h-full w-3/4 rounded-full bg-primary/70" />
)}
</div>
{/* Content cards — rise in, highlight in sequence */}
<div className="mt-auto grid grid-cols-3 gap-1.5 sm:gap-2">
{screenBlocks.map((block, i) => (
<motion.div
key={block.label}
className="rounded-md border border-surface bg-muted/30 p-1.5 sm:rounded-lg sm:p-2"
animate={
reduceMotion
? undefined
: {
y: [4, 0, -3, 0, 4],
opacity: [0.35, 1, 1, 0.85, 0.35],
}
}
transition={
reduceMotion
? undefined
: {
duration: 10,
repeat: Infinity,
ease: screenLoopEase,
delay: 0.35 + i * 0.45,
}
}
>
<motion.div
className="mb-1.5 size-3 rounded bg-primary/35 sm:size-3.5"
animate={
reduceMotion
? undefined
: { scale: [1, 1.15, 1], opacity: [0.55, 1, 0.55] }
}
transition={
reduceMotion
? undefined
: {
duration: 3.6,
repeat: Infinity,
ease: screenLoopEase,
delay: i * 0.5,
}
}
/>
<motion.div
className={cn(
"h-1 origin-right rounded-full bg-foreground/50 sm:h-1.5",
block.w
)}
animate={
reduceMotion
? undefined
: { scaleX: [0.7, 1, 0.85, 1], opacity: [0.5, 1, 0.75, 1] }
}
transition={
reduceMotion
? undefined
: {
duration: 4.2,
repeat: Infinity,
ease: screenLoopEase,
delay: 0.2 + i * 0.35,
}
}
/>
<div className="mt-1 h-1 w-[70%] rounded-full bg-muted-foreground/25" />
</motion.div>
))}
</div>
</div>
{/* Design cursor — explores nav → CTA click → cards */}
{!reduceMotion && (
<motion.div
className="pointer-events-none absolute z-10 will-change-transform"
aria-hidden
animate={{
opacity: [0, 1, 1, 1, 1, 1, 1, 0],
x: ["78%", "18%", "22%", "22%", "55%", "78%", "78%", "78%"],
y: ["18%", "14%", "34%", "34%", "78%", "72%", "18%", "18%"],
scale: [1, 1, 1, 0.84, 1, 1, 1, 1],
}}
transition={{
duration: 10,
repeat: Infinity,
ease: screenLoopEase,
times: [0, 0.16, 0.32, 0.4, 0.58, 0.74, 0.9, 1],
}}
>
<DesignCursor />
</motion.div>
)}
</div>
</div>
);
}
function DesignCursor() {
return (
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
className="drop-shadow-sm"
>
<path
d="M5.5 3.5 19 12.2l-6.1 1.4 1.6 6.4-3.1 1.1-1.7-6.6L5.5 3.5Z"
className="fill-foreground stroke-background"
strokeWidth="1.25"
strokeLinejoin="round"
/>
</svg>
);
}