Build & Push Docker Image / build (push) Has been cancelled
Refresh the homepage around new hero/services/roadmap sections, shared design tokens, and updated brand assets. Co-authored-by: Cursor <cursoragent@cursor.com>
712 lines
26 KiB
TypeScript
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>
|
|
);
|
|
}
|