فراخوان روی منظره
پیشنمایش تمامصفحهبخشِ فراخوانِ پایانی و فوتر روی منظرهٔ دره و تپههای لایهلایه (SVG با توکنها): جملهٔ برند، فرمِ ایمیلِ شیشهای با خطا و موفقیت، و ستونهای لینک.
۱. نصب کامپوننتها
این بلاک از ۴ آیتم دیگ استفاده میکند: buttoncontrol-sizeiconsinput
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/input.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/input.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/input.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/input.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
cta-landscape.tsx
"use client"
import * as React from "react"
import { ArrowUpIcon, CheckCircleIcon, SparklesIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Input } from "@/components/ui/input"
const COLUMNS = [
{ title: "محصول", links: ["چطور کار میکند", "نمونهها", "امنیت", "تعرفه"] },
{ title: "توسعهدهندگان", links: ["مستندات", "مرجعِ API", "وضعیتِ سرویس"] },
{ title: "شرکت", links: ["درباره ما", "تماس"] },
]
/**
* فراخوانِ پایانی روی منظره: دره و تپههای لایهلایه با مه، جملهٔ برند وسطِ آسمان و فرمِ
* ایمیلِ شیشهای؛ فوتر روی تپههای تیرهٔ پایین مینشیند. منظره با SVG و توکنهای رنگ کشیده
* شده، پس با تم و حالتِ تیره هماهنگ میماند و عکس لازم ندارد.
*/
export default function CtaLandscapeBlock() {
const [email, setEmail] = React.useState("")
const [state, setState] = React.useState<"idle" | "error" | "done">("idle")
const submit = (event: React.FormEvent) => {
event.preventDefault()
setState(/^\S+@\S+\.\S+$/.test(email) ? "done" : "error")
}
return (
<ControlSizeProvider size="xl" asChild>
<footer className="relative isolate flex w-full flex-col overflow-hidden bg-[linear-gradient(180deg,var(--dig-slate-500)_0%,var(--dig-slate-300)_38%,var(--dig-green-200)_60%)] text-[var(--dig-white)] dark:bg-[linear-gradient(180deg,var(--dig-slate-950)_0%,var(--dig-slate-800)_40%,var(--dig-green-900)_62%)]">
<svg aria-hidden viewBox="0 0 1440 900" preserveAspectRatio="xMidYMax slice" className="absolute inset-0 -z-10 size-full">
<defs>
<linearGradient id="cl-mist" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stopColor="var(--dig-white)" stopOpacity="0" />
<stop offset="1" stopColor="var(--dig-white)" stopOpacity="0.35" />
</linearGradient>
</defs>
<circle cx="1080" cy="250" r="110" className="fill-[var(--dig-amber-100)] dark:fill-[var(--dig-slate-600)]" />
<path d="M0 470 C 180 380 320 420 470 360 S 760 300 900 380 S 1200 330 1440 400 V900 H0Z" fill="var(--dig-slate-400)" className="dark:fill-[var(--dig-slate-700)]" />
<path d="M0 470 H1440 V560 H0Z" fill="url(#cl-mist)" />
<path d="M0 540 C 220 470 380 520 560 470 S 900 430 1060 500 S 1300 470 1440 520 V900 H0Z" fill="var(--dig-green-700)" className="dark:fill-[var(--dig-green-900)]" />
<path d="M0 620 C 260 560 420 600 640 560 S 1000 540 1180 600 S 1360 590 1440 610 V900 H0Z" fill="var(--dig-green-800)" className="dark:fill-[var(--dig-green-950)]" />
<path d="M0 700 C 300 650 520 690 760 660 S 1200 650 1440 690 V900 H0Z" fill="var(--dig-green-950)" className="dark:fill-[var(--dig-graphite-950)]" />
{Array.from({ length: 48 }, (_, index) => {
const x = (index * 97) % 1440
const h = 18 + ((index * 37) % 30)
return <path key={index} d={`M${x} 700 q ${(index % 2 ? 6 : -6)} ${-h / 2} ${(index % 3) - 1} ${-h}`} strokeWidth="2" className="stroke-[var(--dig-green-800)] dark:stroke-[var(--dig-green-900)]" fill="none" strokeLinecap="round" />
})}
</svg>
<section aria-labelledby="cl-title" className="mx-auto flex w-full max-w-3xl flex-col items-center gap-8 px-4 pt-28 pb-40 text-center sm:px-6 sm:pt-36 sm:pb-56">
<h2 id="cl-title" className="text-3xl leading-snug font-black text-balance [text-shadow:0_2px_24px_var(--dig-slate-900)] sm:text-4xl">
سامانهای که با تیمِ شما بزرگ میشود، نه اینکه جایش را بگیرند
</h2>
{state === "done" ? (
<p role="status" className="flex items-center gap-2 rounded-full bg-[color-mix(in_oklab,var(--dig-graphite-950)_55%,transparent)] px-5 py-3 font-semibold backdrop-blur-md">
<CheckCircleIcon aria-hidden className="size-5 text-[var(--dig-green-300)]" />
در فهرست هستید؛ بهزودی خبرتان میکنیم.
</p>
) : (
<form noValidate onSubmit={submit} className="flex w-full max-w-md flex-col gap-2">
<div className="flex items-center gap-2 rounded-full border border-[color-mix(in_oklab,var(--dig-white)_25%,transparent)] bg-[color-mix(in_oklab,var(--dig-graphite-950)_45%,transparent)] p-1.5 backdrop-blur-md">
<Input
type="email"
dir="ltr"
aria-label="ایمیلِ کاری"
placeholder="name@company.ir"
value={email}
onValueChange={setEmail}
aria-invalid={state === "error"}
radius="full"
className="flex-1 border-0 bg-transparent text-[var(--dig-white)] shadow-none placeholder:text-[var(--dig-neutral-300)]"
/>
<Button type="submit" radius="full" className="shrink-0 [--primary-foreground:var(--dig-graphite-950)] [--primary:var(--dig-white)]">
<SparklesIcon />
دسترسیِ زودهنگام
</Button>
</div>
{state === "error" && (
<p role="alert" className="text-sm font-semibold text-[var(--dig-red-200)]">
ایمیل را کامل بنویسید، مثلاً name@company.ir
</p>
)}
</form>
)}
</section>
<div className="mx-auto grid w-full max-w-6xl grid-cols-2 gap-8 px-4 pb-10 sm:px-6 md:grid-cols-[1fr_auto_auto_auto] md:gap-16">
<div className="col-span-2 flex items-start md:col-span-1">
<span aria-hidden className="flex size-12 items-center justify-center rounded-xl bg-[var(--dig-white)] text-[var(--dig-green-900)] [&>svg]:size-6">
<SparklesIcon />
</span>
</div>
{COLUMNS.map((column) => (
<nav key={column.title} aria-label={column.title} className="flex flex-col gap-3 text-sm">
<span className="font-bold">{column.title}</span>
{column.links.map((link) => (
<a key={link} href="#" className="text-[var(--dig-neutral-300)] hover:text-[var(--dig-white)]">
{link}
</a>
))}
</nav>
))}
</div>
<div className="mx-auto flex w-full max-w-6xl items-center justify-between gap-4 border-t border-[color-mix(in_oklab,var(--dig-white)_15%,transparent)] px-4 py-6 text-sm text-[var(--dig-neutral-400)] sm:px-6">
<span>© ۱۴۰۵ آزمایشگاهِ دره. همهٔ حقوق محفوظ است.</span>
<a href="#top" className="flex items-center gap-1 hover:text-[var(--dig-white)]">
بالا
<ArrowUpIcon aria-hidden className="size-4" />
</a>
</div>
</footer>
</ControlSizeProvider>
)
}