feat: pretify template

This commit is contained in:
2026-05-08 22:39:33 +03:00
parent ff296dd1c3
commit dce25f7123
4 changed files with 69 additions and 902 deletions

View File

@@ -6,7 +6,12 @@ B2B SaaS-шаблон для AI support platform с pricing, customers, resource
| Route | Page | Blocks and copy intent | | Route | Page | Blocks and copy intent |
| --- | --- | --- | | --- | --- | --- |
| `/` | Главная | Launch-style hero, AI support workflow, platform cards, customer proof. |\n| `/product` | Product | Issue detection, agent workflow, integrations, QA and analytics. |\n| `/pricing` | Pricing | SaaS pricing tiers, token/seat messaging, FAQ. |\n| `/customers` | Customers | Case studies, logos, quotes, business outcomes. |\n| `/resources` | Resources | Articles, guides, webinars, playbooks. |\n| `/dashboard` | Dashboard | Demo support dashboard with charts, queues and health cards. | | `/` | Главная | Launch-style hero, AI support workflow, platform cards, customer proof. |
| `/product` | Product | Issue detection, agent workflow, integrations, QA and analytics. |
| `/pricing` | Pricing | SaaS pricing tiers, token/seat messaging, FAQ. |
| `/customers` | Customers | Case studies, logos, quotes, business outcomes. |
| `/resources` | Resources | Articles, guides, webinars, playbooks. |
| `/dashboard` | Dashboard | Demo support dashboard with charts, queues and health cards. |
## Visual Direction ## Visual Direction
@@ -24,3 +29,8 @@ B2B SaaS-шаблон для AI support platform с pricing, customers, resource
- Добавлены доменные production-блоки поверх базовой структуры: richer hero/product/dashboard sections, сценарии принятия решения, trust/operations blocks. - Добавлены доменные production-блоки поверх базовой структуры: richer hero/product/dashboard sections, сценарии принятия решения, trust/operations blocks.
- Все важные CTA и пользовательские лейблы держатся в JSX или typed content arrays, чтобы визуальный редактор Fluw мог находить и менять тексты. - Все важные CTA и пользовательские лейблы держатся в JSX или typed content arrays, чтобы визуальный редактор Fluw мог находить и менять тексты.
- Шрифты выбраны с поддержкой кириллицы; latin-only display fonts не используются. - Шрифты выбраны с поддержкой кириллицы; latin-only display fonts не используются.
## Premium pass
- Переработаны hero/карточки/доменные секции: меньше generic UI, больше сценарной пользы и визуального характера.
- Сохранены node_modules и .next для следующих этапов локальной проверки.

View File

@@ -144,3 +144,27 @@
linear-gradient(135deg, oklch(0.92 0.22 116), oklch(0.78 0.2 196)); linear-gradient(135deg, oklch(0.92 0.22 116), oklch(0.78 0.2 196));
animation: grainient-shift 12s ease-in-out infinite; animation: grainient-shift 12s ease-in-out infinite;
} }
.grainient-field::after {
content: "";
position: absolute;
inset: 0;
background-image: radial-gradient(circle at 1px 1px, rgba(255,255,255,.7) 1px, transparent 0);
background-size: 18px 18px;
opacity: .28;
}
.signal-aurora {
background:
radial-gradient(circle at 14% 12%, oklch(0.91 0.24 132 / 86%), transparent 30%),
radial-gradient(circle at 82% 18%, oklch(0.82 0.21 190 / 76%), transparent 34%),
radial-gradient(circle at 54% 82%, oklch(0.80 0.18 264 / 42%), transparent 36%),
linear-gradient(135deg, oklch(0.96 0.13 122), oklch(0.85 0.16 192));
animation: grainient-shift 14s ease-in-out infinite;
}
.signal-grid {
background-image: radial-gradient(circle at 1px 1px, rgba(255,255,255,.64) 1px, transparent 0);
background-size: 20px 20px;
opacity: .42;
mask-image: linear-gradient(180deg, black 0%, black 68%, transparent 100%);
}

View File

@@ -11,11 +11,11 @@ import {
export const site = { export const site = {
name: "SignalDesk AI", name: "SignalDesk AI",
tagline: "AI-агенты, real-time issue detection и QA для команд поддержки, которые растут быстрее очереди тикетов.", tagline: "AI-агенты, real-time issue detection и QA для support-команд, которые растут быстрее очереди тикетов.",
cta: "Запустить демо", cta: "Запустить демо",
secondaryCta: "Смотреть панель", secondaryCta: "Смотреть панель",
heroImage: "https://images.unsplash.com/photo-1551434678-e076c223a692?auto=format&fit=crop&w=1400&q=80", heroImage: "https://images.unsplash.com/photo-1551434678-e076c223a692?auto=format&fit=crop&w=1400&q=80",
accentImage: "https://images.unsplash.com/photo-1552664730-d307ca884978?auto=format&fit=crop&w=1100&q=80", accentImage: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&w=1100&q=80",
nav: [ nav: [
{ href: "/product", label: "Продукт" }, { href: "/product", label: "Продукт" },
{ href: "/pricing", label: "Тарифы" }, { href: "/pricing", label: "Тарифы" },
@@ -27,15 +27,15 @@ export const site = {
export const highlights = [ export const highlights = [
{ title: "Deflection", value: "42%", text: "AI закрывает повторяющиеся обращения без потери контроля.", icon: BotIcon }, { title: "Deflection", value: "42%", text: "AI закрывает повторяющиеся обращения без потери контроля.", icon: BotIcon },
{ title: "Detection", value: "30 сек", text: "Система замечает всплеск проблем до массовой эскалации.", icon: ActivityIcon }, { title: "Detection", value: "30 сек", text: "Команда видит всплеск проблемы до массовой эскалации.", icon: ActivityIcon },
{ title: "QA coverage", value: "100%", text: "Проверка разговоров, тональности и compliance-сигналов.", icon: ShieldCheckIcon }, { title: "QA coverage", value: "100%", text: "Проверка разговоров, тональности и compliance-сигналов.", icon: ShieldCheckIcon },
] as const; ] as const;
export const products = [ export const products = [
{ name: "AI-помощник входящей очереди", price: "agent layer", tag: "automation", text: "Черновики ответов, summary, handoff и подсказки оператору." }, { name: "AI-помощник оператора", price: "agent layer", tag: "automation", text: "Черновики ответов, summary, handoff и next best action." },
{ name: "Радар проблем", price: "real time", tag: "detection", text: "Кластеры проблем, topic volume и affected conversations." }, { name: "Радар проблем", price: "real time", tag: "detection", text: "Кластеры тем, topic volume и affected conversations." },
{ name: "QA-мониторинг", price: "always on", tag: "quality", text: "Покрытие всех диалогов вместо выборочной ручной проверки." }, { name: "QA-мониторинг", price: "always on", tag: "quality", text: "Оценка всех диалогов вместо ручной выборки." },
{ name: "Сохранение выручки", price: "retention", tag: "business", text: "Сигналы churn risk, refunds и missed SLA в одном dashboard." }, { name: "Revenue risk", price: "retention", tag: "business", text: "Сигналы churn risk, refunds и missed SLA в одном dashboard." },
] as const; ] as const;
export const dishes = products; export const dishes = products;
@@ -47,18 +47,18 @@ export const tastingSets = [
] as const; ] as const;
export const testimonials = [ export const testimonials = [
{ name: "MintCart", text: "Мы увидели системные проблемы доставки раньше, чем они стали refund wave.", rating: "38% fewer escalations" }, { name: "MintCart", text: "Мы увидели системные проблемы доставки раньше, чем они стали refund wave.", rating: "-38% escalations" },
{ name: "CloudDesk", text: "QA перестал быть выборкой. Теперь команда видит всю картину.", rating: "100% QA coverage" }, { name: "CloudDesk", text: "QA перестал быть выборкой. Теперь команда видит всю картину.", rating: "100% QA coverage" },
] as const; ] as const;
export const eventTypes = [ export const eventTypes = [
{ title: "Connect inbox", text: "Подключите support channels и импортируйте историю тем.", icon: MessageSquareTextIcon }, { title: "Подключить inbox", text: "Support channels, история тем и SLA попадают в единую очередь.", icon: MessageSquareTextIcon },
{ title: "Train playbooks", text: "Задайте tone, escalation rules и policy snippets.", icon: CheckCircle2Icon }, { title: "Настроить playbooks", text: "Tone, escalation rules и policy snippets контролируют AI-ответы.", icon: CheckCircle2Icon },
{ title: "Monitor outcomes", text: "Смотрите SLA, sentiment, deflection и risk topics.", icon: BarChart3Icon }, { title: "Измерять outcome", text: "SLA, sentiment, deflection и risk topics видны в dashboard.", icon: BarChart3Icon },
] as const; ] as const;
export const contactCards = [ export const contactCards = [
{ title: "Live queue", text: "248 активных conversations, 18 high priority", icon: LifeBuoyIcon }, { title: "Live queue", text: "248 активных conversations, 18 high priority", icon: LifeBuoyIcon },
{ title: "SLA health", text: "96% within target, 4 risky segments", icon: GaugeIcon }, { title: "SLA health", text: "96% within target, 4 risky segments", icon: GaugeIcon },
{ title: "Topic spike", text: "Payment retries +27% за последние 2 часа", icon: ActivityIcon }, { title: "Topic spike", text: "Payment retries +27% за последние 2 часа", icon: ActivityIcon },
] as const; ] as const;

View File

@@ -2,919 +2,52 @@
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useSyncExternalStore } from "react"; import { ArrowRightIcon, CheckIcon, MenuIcon, ShieldCheckIcon, SparklesIcon } from "lucide-react";
import {
Area,
AreaChart,
Bar,
BarChart,
CartesianGrid,
Cell,
Pie,
PieChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
import {
ArrowRightIcon,
BellIcon,
CheckIcon,
ChevronRightIcon,
ФильтрIcon,
MenuIcon,
SearchIcon,
ShoppingCartIcon,
SlidersHorizontalIcon,
StarIcon,
UserRoundIcon,
XIcon,
} from "lucide-react";
import { site } from "@/entities/site-content"; import { eventTypes, highlights, site, testimonials } from "@/entities/site-content";
import { Button } from "@/shared/ui/button";
import { Input } from "@/shared/ui/input";
import { Textarea } from "@/shared/ui/textarea";
import { Badge } from "@/shared/ui/badge"; import { Badge } from "@/shared/ui/badge";
import { Card, CardContent } from "@/shared/ui/card"; import { Button } from "@/shared/ui/button";
import { Separator } from "@/shared/ui/separator"; import { Separator } from "@/shared/ui/separator";
import { Progress } from "@/shared/ui/progress";
type IconComponent = React.ComponentType<{ className?: string }>; type IconComponent = React.ComponentType<{ className?: string }>;
type TextItem = { name: string; price: string; tag: string; text: string };
type TileItem = { title: string; price: string; items: readonly string[] };
type IconItem = { title: string; text: string; icon: IconComponent };
interface MetricItem { const bars = [18, 26, 22, 42, 58, 48, 72, 80, 62, 88, 96, 76];
title: string;
value: string;
text: string;
icon: IconComponent;
}
interface TextItem {
name: string;
price: string;
tag: string;
text: string;
}
interface TileItem {
title: string;
price: string;
items: readonly string[];
}
interface TestimonialItem {
name: string;
text: string;
rating: string;
}
interface IconItem {
title: string;
text: string;
icon: IconComponent;
}
const revenueData = [
{ name: "Mon", value: 28, alt: 20 },
{ name: "Tue", value: 34, alt: 24 },
{ name: "Wed", value: 31, alt: 28 },
{ name: "Thu", value: 46, alt: 32 },
{ name: "Fri", value: 52, alt: 36 },
{ name: "Sat", value: 43, alt: 31 },
{ name: "Sun", value: 58, alt: 42 },
] as const;
const pieData = [
{ name: "Complete", value: 56, fill: "var(--primary)" },
{ name: "At risk", value: 24, fill: "var(--accent)" },
{ name: "Open", value: 20, fill: "var(--muted)" },
] as const;
function subscribeToClient(onStoreChange: () => void) {
const frame = window.requestAnimationFrame(onStoreChange);
return () => window.cancelAnimationFrame(frame);
}
function ClientChart({ children }: { children: React.ReactNode }) {
const mounted = useSyncExternalStore(
subscribeToClient,
() => true,
() => false,
);
if (!mounted) {
return <div className="h-full w-full rounded-md bg-muted" />;
}
return <>{children}</>;
}
export function SiteHeader() { export function SiteHeader() {
return ( return <header className="sticky top-0 z-50 border-b border-border bg-background/90 backdrop-blur-xl"><div className="mx-auto flex h-[72px] w-full max-w-[1280px] items-center justify-between px-5 sm:px-8"><Link href="/" className="flex items-center gap-3"><span className="flex size-10 items-center justify-center rounded-md bg-primary text-sm font-bold text-primary-foreground">Si</span><span className="text-xl font-semibold">{site.name}</span></Link><nav className="hidden items-center gap-7 md:flex">{site.nav.map((item) => <Link key={item.href} href={item.href} className="text-sm font-semibold text-muted-foreground hover:text-foreground">{item.label}</Link>)}</nav><div className="flex items-center gap-2"><Button asChild className="hidden h-11 rounded-md px-6 sm:inline-flex"><Link href="/dashboard">{site.cta}</Link></Button><Button variant="outline" size="icon" className="rounded-md md:hidden" aria-label="Открыть меню"><MenuIcon className="size-4" /></Button></div></div></header>;
<header className="sticky top-0 z-50 border-b border-border/70 bg-background/88 backdrop-blur-xl">
<div className="mx-auto flex h-16 w-full max-w-[1180px] items-center justify-between px-4 sm:px-6">
<Link href="/" className="flex items-center gap-3">
<span className="flex size-9 items-center justify-center rounded-md bg-primary text-sm font-bold text-primary-foreground">
{site.name.slice(0, 2)}
</span>
<span className="font-display text-lg font-semibold">{site.name}</span>
</Link>
<nav className="hidden items-center gap-1 md:flex">
{site.nav.map((item) => (
<Link
key={item.href}
href={item.href}
className="rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition hover:bg-muted hover:text-foreground"
>
{item.label}
</Link>
))}
</nav>
<div className="flex items-center gap-2">
<Button asChild className="hidden h-10 rounded-md sm:inline-flex">
<Link href={site.nav[1]?.href ?? "/"}>{site.cta}</Link>
</Button>
<Button variant="outline" size="icon" className="rounded-md md:hidden" aria-label="Open menu">
<MenuIcon className="size-4" />
</Button>
</div>
</div>
</header>
);
} }
export function SiteFooter() { export function SiteFooter() { return <footer className="border-t border-border bg-card"><div className="mx-auto grid w-full max-w-[1280px] gap-8 px-5 py-12 sm:px-8 md:grid-cols-[1.4fr_1fr_1fr]"><div><div className="text-2xl font-semibold">{site.name}</div><p className="mt-4 max-w-[460px] text-sm leading-6 text-muted-foreground">{site.tagline}</p></div><div className="grid gap-2 text-sm text-muted-foreground"><div className="mb-2 font-semibold text-foreground">Страницы</div>{site.nav.slice(0,4).map((item) => <Link key={item.href} href={item.href}>{item.label}</Link>)}</div><div className="text-sm leading-6 text-muted-foreground"><div className="mb-2 font-semibold text-foreground">Шаблон</div>B2B SaaS лендинг с launch hero, workflow, pricing, customer stories и demo dashboard.</div></div></footer>; }
return (
<footer className="border-t border-border bg-card">
<div className="mx-auto grid w-full max-w-[1180px] gap-8 px-4 py-10 sm:px-6 md:grid-cols-[1.4fr_1fr_1fr]">
<div>
<div className="mb-3 font-display text-2xl font-semibold">{site.name}</div>
<p className="max-w-[460px] text-sm leading-6 text-muted-foreground">{site.tagline}</p>
</div>
<div>
<div className="mb-3 text-sm font-semibold">Страницы</div>
<div className="grid gap-2 text-sm text-muted-foreground">
{site.nav.slice(0, 4).map((item) => (
<Link key={item.href} href={item.href} className="hover:text-foreground">
{item.label}
</Link>
))}
</div>
</div>
<div>
<div className="mb-3 text-sm font-semibold">Для AI-правок</div>
<p className="text-sm leading-6 text-muted-foreground">
Статический production-ready шаблон: тексты, секции и UI можно менять через визуальное редактирование Fluw.
</p>
</div>
</div>
</footer>
);
}
export function PageHero({
eyebrow,
title,
text,
primaryHref,
primaryLabel,
secondaryHref,
secondaryLabel,
image,
}: {
eyebrow: string;
title: string;
text: string;
primaryHref: string;
primaryLabel: string;
secondaryHref: string;
secondaryLabel: string;
image: string;
}) {
return (
<section className="mx-auto grid min-h-[calc(100vh-4rem)] w-full max-w-[1180px] items-center gap-10 px-4 py-12 sm:px-6 lg:grid-cols-[0.95fr_1.05fr]">
<div>
<Badge variant="outline" className="mb-5 rounded-md border-primary/30 bg-primary/5 text-primary">
{eyebrow}
</Badge>
<h1 className="font-display text-5xl font-semibold leading-[0.98] tracking-normal sm:text-6xl lg:text-7xl">
{title}
</h1>
<p className="mt-6 max-w-[620px] text-base leading-7 text-muted-foreground sm:text-lg">{text}</p>
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
<Button asChild className="h-11 rounded-md px-5">
<Link href={primaryHref}>
{primaryLabel}
<ArrowRightIcon className="size-4" />
</Link>
</Button>
<Button asChild variant="outline" className="h-11 rounded-md px-5">
<Link href={secondaryHref}>{secondaryLabel}</Link>
</Button>
</div>
</div>
<div className="relative min-h-[420px] overflow-hidden rounded-lg border border-border bg-muted">
<Image src={image} alt="" fill priority className="object-cover" sizes="(min-width: 1024px) 560px, 100vw" />
<div className="absolute bottom-4 left-4 right-4 rounded-md border border-white/30 bg-white/82 p-4 text-foreground shadow-lg backdrop-blur">
<div className="flex items-center justify-between gap-4">
<div>
<div className="text-sm font-semibold">Готовый шаблон</div>
<div className="text-xs text-muted-foreground">Несколько страниц, редактируемые тексты, production-логика блоков</div>
</div>
<StarIcon className="size-5 fill-primary text-primary" />
</div>
</div>
</div>
</section>
);
}
export function GrainientHero() { export function GrainientHero() {
return ( return <section className="relative overflow-hidden border-b border-border bg-background"><div className="signal-aurora absolute inset-0" /><div className="signal-grid absolute inset-0" /><div className="relative mx-auto grid min-h-[720px] w-full max-w-[1280px] items-center gap-10 px-5 py-16 sm:px-8 lg:grid-cols-[0.86fr_1.14fr]"><div><Badge className="mb-6 rounded-md bg-background text-foreground shadow-sm">60+ обновлений продукта</Badge><h1 className="max-w-[760px] text-5xl font-semibold leading-[0.92] tracking-normal sm:text-7xl lg:text-[5.7rem]">Support-команда видит проблему раньше очереди</h1><p className="mt-7 max-w-[620px] text-lg leading-8 text-foreground/72">{site.tagline}</p><div className="mt-8 flex flex-col gap-3 sm:flex-row"><Button asChild className="h-12 rounded-md bg-foreground px-6 text-background hover:bg-foreground/90"><Link href="/dashboard">Запустить демо <ArrowRightIcon className="size-4" /></Link></Button><Button asChild variant="outline" className="h-12 rounded-md bg-background/50 px-6"><Link href="/product">Как работает</Link></Button></div></div><div className="relative"><div className="absolute -left-5 top-9 z-10 hidden rounded-md bg-foreground px-3 py-2 text-xs font-semibold text-background shadow-xl lg:block">topic spike +27%</div><SaasDashboard compact /></div></div></section>;
<section className="relative overflow-hidden border-b border-border">
<div className="grainient-field absolute inset-x-0 top-0 h-[520px] opacity-90 blur-0" />
<div className="relative mx-auto grid min-h-[calc(100vh-4rem)] w-full max-w-[1180px] items-center gap-10 px-4 py-12 sm:px-6 lg:grid-cols-[0.9fr_1.1fr]">
<div>
<Badge className="mb-5 rounded-md bg-white text-foreground">60+ обновлений продукта</Badge>
<h1 className="font-display text-5xl font-semibold leading-[0.95] tracking-normal sm:text-7xl lg:text-8xl">
{site.name} для команд поддержки
</h1>
<p className="mt-6 max-w-[620px] text-base leading-7 text-foreground/72 sm:text-lg">{site.tagline}</p>
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
<Button asChild className="h-11 rounded-md bg-foreground px-5 text-background hover:bg-foreground/90">
<Link href="/pricing">{site.cta}</Link>
</Button>
<Button asChild variant="outline" className="h-11 rounded-md border-foreground/30 bg-white/50 px-5">
<Link href="/dashboard">{site.secondaryCta}</Link>
</Button>
</div>
</div>
<div className="rounded-lg border border-white/50 bg-white/80 p-4 shadow-2xl backdrop-blur">
<SaasDashboard compact />
</div>
</div>
</section>
);
}
export function InnerHero({ eyebrow, title, text }: { eyebrow: string; title: string; text: string }) {
return (
<section className="border-b border-border bg-card">
<div className="mx-auto w-full max-w-[1180px] px-4 py-14 sm:px-6 sm:py-18">
<Badge variant="outline" className="mb-5 rounded-md border-primary/30 text-primary">
{eyebrow}
</Badge>
<h1 className="max-w-[900px] font-display text-4xl font-semibold leading-tight tracking-normal sm:text-6xl">
{title}
</h1>
<p className="mt-5 max-w-[760px] text-base leading-7 text-muted-foreground sm:text-lg">{text}</p>
</div>
</section>
);
}
export function MetricStrip({ items }: { items: readonly MetricItem[] }) {
return (
<section className="mx-auto grid w-full max-w-[1180px] gap-3 px-4 py-8 sm:px-6 md:grid-cols-3">
{items.map((item) => {
const Icon = item.icon;
return (
<Card key={item.title} className="rounded-lg border-border bg-card shadow-none">
<CardContent className="p-5">
<div className="mb-5 flex size-10 items-center justify-center rounded-md bg-muted text-primary">
<Icon className="size-5" />
</div>
<div className="font-display text-4xl font-semibold">{item.value}</div>
<h3 className="mt-2 text-base font-semibold">{item.title}</h3>
<p className="mt-2 text-sm leading-6 text-muted-foreground">{item.text}</p>
</CardContent>
</Card>
);
})}
</section>
);
}
export function FeaturedGrid({
eyebrow,
title,
text,
items,
}: {
eyebrow: string;
title: string;
text: string;
items: readonly TextItem[];
}) {
return (
<section className="mx-auto w-full max-w-[1180px] px-4 py-12 sm:px-6">
<div className="mb-8 grid gap-4 md:grid-cols-[0.8fr_1fr]">
<div>
<div className="mb-2 text-xs font-semibold uppercase text-primary">{eyebrow}</div>
<h2 className="font-display text-3xl font-semibold leading-tight sm:text-5xl">{title}</h2>
</div>
<p className="max-w-[620px] text-sm leading-7 text-muted-foreground sm:text-base">{text}</p>
</div>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
{items.map((item) => (
<article key={item.name} className="rounded-lg border border-border bg-card p-5">
<div className="mb-4 flex items-center justify-between gap-3">
<Badge variant="secondary" className="rounded-md">
{item.tag}
</Badge>
<span className="text-sm font-semibold">{item.price}</span>
</div>
<h3 className="text-lg font-semibold">{item.name}</h3>
<p className="mt-3 text-sm leading-6 text-muted-foreground">{item.text}</p>
</article>
))}
</div>
</section>
);
}
export function SplitStory({
image,
eyebrow,
title,
text,
points,
}: {
image: string;
eyebrow: string;
title: string;
text: string;
points: readonly string[];
}) {
return (
<section className="mx-auto grid w-full max-w-[1180px] gap-8 px-4 py-12 sm:px-6 lg:grid-cols-2">
<div className="relative min-h-[380px] overflow-hidden rounded-lg border border-border bg-muted">
<Image src={image} alt="" fill className="object-cover" sizes="(min-width: 1024px) 560px, 100vw" />
</div>
<div className="flex flex-col justify-center">
<div className="mb-2 text-xs font-semibold uppercase text-primary">{eyebrow}</div>
<h2 className="font-display text-3xl font-semibold leading-tight sm:text-5xl">{title}</h2>
<p className="mt-5 text-base leading-7 text-muted-foreground">{text}</p>
<div className="mt-6 grid gap-3">
{points.map((point) => (
<div key={point} className="flex items-center gap-3 text-sm font-medium">
<span className="flex size-6 items-center justify-center rounded-md bg-primary text-primary-foreground">
<CheckIcon className="size-4" />
</span>
{point}
</div>
))}
</div>
</div>
</section>
);
}
export function TestimonialBand({ items }: { items: readonly TestimonialItem[] }) {
return (
<section className="mx-auto w-full max-w-[1180px] px-4 py-12 sm:px-6">
<div className="grid gap-3 md:grid-cols-2">
{items.map((item) => (
<blockquote key={item.name} className="rounded-lg border border-border bg-card p-6">
<div className="mb-4 text-sm font-semibold text-primary">{item.rating}</div>
<p className="font-display text-2xl leading-tight">{item.text}</p>
<footer className="mt-5 text-sm text-muted-foreground">{item.name}</footer>
</blockquote>
))}
</div>
</section>
);
}
export function PricingTiles({ title, items }: { title: string; items: readonly TileItem[] }) {
return (
<section className="mx-auto w-full max-w-[1180px] px-4 py-12 sm:px-6">
<h2 className="mb-6 font-display text-3xl font-semibold sm:text-5xl">{title}</h2>
<div className="grid gap-3 md:grid-cols-3">
{items.map((item) => (
<article key={item.title} className="rounded-lg border border-border bg-card p-6">
<h3 className="text-xl font-semibold">{item.title}</h3>
<div className="mt-3 font-display text-3xl font-semibold">{item.price}</div>
<Separator className="my-5" />
<div className="grid gap-2">
{item.items.map((line) => (
<div key={line} className="flex items-center gap-2 text-sm text-muted-foreground">
<CheckIcon className="size-4 text-primary" />
{line}
</div>
))}
</div>
</article>
))}
</div>
</section>
);
}
export function IconCards({ items }: { items: readonly IconItem[] }) {
return (
<section className="mx-auto grid w-full max-w-[1180px] gap-3 px-4 py-10 sm:px-6 md:grid-cols-3">
{items.map((item) => {
const Icon = item.icon;
return (
<article key={item.title} className="rounded-lg border border-border bg-card p-5">
<div className="mb-4 flex size-10 items-center justify-center rounded-md bg-muted text-primary">
<Icon className="size-5" />
</div>
<h3 className="text-lg font-semibold">{item.title}</h3>
<p className="mt-2 text-sm leading-6 text-muted-foreground">{item.text}</p>
</article>
);
})}
</section>
);
}
export function InfoColumns({ title, items }: { title: string; items: readonly { title: string; text: string }[] }) {
return (
<section className="mx-auto w-full max-w-[1180px] px-4 py-10 sm:px-6">
<h2 className="mb-5 font-display text-3xl font-semibold">{title}</h2>
<div className="grid gap-3 md:grid-cols-3">
{items.map((item) => (
<article key={item.title} className="rounded-lg border border-border bg-card p-5">
<h3 className="font-semibold">{item.title}</h3>
<p className="mt-2 text-sm leading-6 text-muted-foreground">{item.text}</p>
</article>
))}
</div>
</section>
);
}
export function CtaPanel({ title, text, href, label }: { title: string; text: string; href: string; label: string }) {
return (
<section className="mx-auto w-full max-w-[1180px] px-4 py-12 sm:px-6">
<div className="rounded-lg border border-border bg-primary p-7 text-primary-foreground sm:p-10">
<h2 className="font-display text-3xl font-semibold sm:text-5xl">{title}</h2>
<p className="mt-4 max-w-[720px] text-sm leading-7 opacity-82 sm:text-base">{text}</p>
<Button asChild variant="secondary" className="mt-6 h-11 rounded-md">
<Link href={href}>
{label}
<ArrowRightIcon className="size-4" />
</Link>
</Button>
</div>
</section>
);
}
export function ReservationForm() {
return (
<section className="mx-auto grid w-full max-w-[1180px] gap-6 px-4 py-12 sm:px-6 lg:grid-cols-[0.9fr_1.1fr]">
<div>
<h2 className="font-display text-3xl font-semibold sm:text-5xl">Оставьте заявку</h2>
<p className="mt-4 text-sm leading-7 text-muted-foreground">
Это статическая форма-шаблон. Она показывает структуру будущего booking flow без отправки данных наружу.
</p>
</div>
<form className="grid gap-3 rounded-lg border border-border bg-card p-5">
<div className="grid gap-3 sm:grid-cols-2">
<Input placeholder="Имя" />
<Input placeholder="Телефон или email" />
<Input placeholder="Дата" />
<Input placeholder="Время" />
</div>
<Textarea placeholder="Комментарий, пожелания или ограничения" />
<Button type="button" className="h-11 rounded-md">Отправить заявку</Button>
</form>
</section>
);
}
export function ContactForm() {
return (
<section className="mx-auto grid w-full max-w-[1180px] gap-6 px-4 py-12 sm:px-6 lg:grid-cols-2">
<div className="rounded-lg border border-border bg-card p-6">
<h2 className="font-display text-3xl font-semibold">Связаться</h2>
<p className="mt-3 text-sm leading-7 text-muted-foreground">
Форма готова как UI-слой, но не подключена к API в рамках статического шаблона.
</p>
</div>
<form className="grid gap-3 rounded-lg border border-border bg-card p-5">
<Input placeholder="Ваше имя" />
<Input placeholder="Email" />
<Textarea placeholder="Сообщение" />
<Button type="button" className="h-11 rounded-md">Отправить</Button>
</form>
</section>
);
}
export function CatalogToolbar() {
return (
<section className="mx-auto flex w-full max-w-[1180px] flex-col gap-3 px-4 py-6 sm:px-6 md:flex-row md:items-center md:justify-between">
<div className="flex flex-1 items-center gap-2 rounded-lg border border-border bg-card px-3">
<SearchIcon className="size-4 text-muted-foreground" />
<Input className="border-0 bg-transparent shadow-none focus-visible:ring-0" placeholder="Поиск по названию, тегу или статусу" />
</div>
<div className="flex gap-2">
<Button variant="outline" className="rounded-md">
<ФильтрIcon className="size-4" />
Фильтр
</Button>
<Button variant="outline" className="rounded-md">
<SlidersHorizontalIcon className="size-4" />
Сортировка
</Button>
</div>
</section>
);
}
export function ProductDetail() {
return (
<section className="mx-auto grid w-full max-w-[1180px] gap-10 px-4 py-12 sm:px-6 lg:grid-cols-[1.05fr_0.95fr]">
<div className="grid gap-3">
<div className="relative min-h-[620px] overflow-hidden rounded-lg border border-border bg-muted">
<Image src={site.heroImage} alt="" fill className="object-cover" sizes="(min-width: 1024px) 560px, 100vw" />
</div>
<div className="grid grid-cols-4 gap-3">
{[site.heroImage, site.accentImage, site.heroImage, site.accentImage].map((image, index) => (
<div key={`${image}-${index}`} className="relative aspect-square overflow-hidden rounded-lg border border-border bg-muted">
<Image src={image} alt="" fill className="object-cover" sizes="150px" />
</div>
))}
</div>
</div>
<div>
<div className="mb-4 flex items-center gap-2 text-sm text-muted-foreground">
Каталог <ChevronRightIcon className="size-4" /> Верхняя одежда <ChevronRightIcon className="size-4" /> Классическая overshirt
</div>
<h1 className="font-display text-4xl font-semibold sm:text-6xl">Классическая overshirt</h1>
<div className="mt-5 flex items-center gap-3">
<Badge variant="outline" className="rounded-md">4.8 </Badge>
<span className="text-sm text-muted-foreground">210 отзывов</span>
</div>
<div className="mt-8 flex items-end gap-4">
<div className="font-display text-4xl font-semibold">17 900 </div>
<div className="pb-1 text-sm text-muted-foreground line-through">22 900 </div>
<Badge className="rounded-md bg-primary/10 text-primary hover:bg-primary/10">Скидка 20%</Badge>
</div>
<p className="mt-6 text-base leading-7 text-muted-foreground">
Плотный cotton twill, прямой крой, hidden placket и аккуратный вес для межсезонья.
</p>
<Separator className="my-7" />
<div className="grid gap-5">
<OptionRow label="Цвет" options={["Черный", "Шалфей", "Охра", "Камень", "Синий"]} />
<OptionRow label="Размер" options={["XS", "S", "M", "L", "XL"]} active="L" />
</div>
<div className="mt-7 grid gap-3 sm:grid-cols-2">
<Button className="h-12 rounded-md"><ShoppingCartIcon className="size-4" /> В корзину</Button>
<Button variant="outline" className="h-12 rounded-md">В избранное</Button>
</div>
</div>
</section>
);
}
function OptionRow({ label, options, active }: { label: string; options: readonly string[]; active?: string }) {
return (
<div>
<div className="mb-3 text-sm font-semibold">{label}</div>
<div className="flex flex-wrap gap-2">
{options.map((option) => (
<Button key={option} variant={option === active ? "default" : "outline"} className="h-9 rounded-md px-4">
{option}
</Button>
))}
</div>
</div>
);
}
export function LookbookGrid() {
return (
<section className="mx-auto grid w-full max-w-[1180px] gap-3 px-4 py-12 sm:px-6 md:grid-cols-4">
{[site.heroImage, site.accentImage, site.heroImage, site.accentImage, site.accentImage, site.heroImage].map((image, index) => (
<div
key={`${image}-look-${index}`}
className={`relative overflow-hidden rounded-lg border border-border bg-muted ${index === 0 || index === 5 ? "md:col-span-2 md:row-span-2 min-h-[420px]" : "min-h-[260px]"}`}
>
<Image src={image} alt="" fill className="object-cover" sizes="(min-width: 768px) 25vw, 100vw" />
</div>
))}
</section>
);
}
export function CartPreview() {
return (
<section className="mx-auto grid w-full max-w-[1180px] gap-6 px-4 py-12 sm:px-6 lg:grid-cols-[1fr_380px]">
<div className="grid gap-3">
{["Classic overshirt", "Wide pleated trouser", "Utility bag"].map((item, index) => (
<div key={item} className="flex items-center gap-4 rounded-lg border border-border bg-card p-4">
<div className="relative size-20 overflow-hidden rounded-md bg-muted">
<Image src={index % 2 === 0 ? site.heroImage : site.accentImage} alt="" fill className="object-cover" sizes="80px" />
</div>
<div className="flex-1">
<div className="font-semibold">{item}</div>
<div className="text-sm text-muted-foreground">Черный / L / Qty 1</div>
</div>
<div className="font-semibold">{index === 0 ? "17 900 ₽" : "9 800 ₽"}</div>
</div>
))}
</div>
<aside className="rounded-lg border border-border bg-card p-5">
<h2 className="font-display text-2xl font-semibold">Итог заказа</h2>
<div className="mt-5 grid gap-3 text-sm">
<div className="flex justify-between"><span>Товары</span><span>37 500 </span></div>
<div className="flex justify-between"><span>Доставка</span><span>Бесплатно</span></div>
<Separator />
<div className="flex justify-between text-lg font-semibold"><span>Итого</span><span>37 500 </span></div>
</div>
<Button className="mt-6 h-11 w-full rounded-md">Перейти к оформлению</Button>
</aside>
</section>
);
}
export function DoctorGrid() {
const doctors = [
["Мария Северова", "Терапевт, preventive care", "12 лет опыта"],
["Илья Данилов", "Кардиолог", "9 лет опыта"],
["Анна Волкова", "Эндокринолог", "11 лет опыта"],
] as const;
return (
<section className="mx-auto grid w-full max-w-[1180px] gap-3 px-4 py-12 sm:px-6 md:grid-cols-3">
{doctors.map(([name, role, experience]) => (
<article key={name} className="rounded-lg border border-border bg-card p-5">
<div className="mb-5 flex size-14 items-center justify-center rounded-md bg-muted text-primary">
<UserRoundIcon className="size-7" />
</div>
<h3 className="text-xl font-semibold">{name}</h3>
<p className="mt-2 text-sm text-muted-foreground">{role}</p>
<Badge variant="outline" className="mt-4 rounded-md">{experience}</Badge>
</article>
))}
</section>
);
} }
export function SaasDashboard({ compact = false }: { compact?: boolean }) { export function SaasDashboard({ compact = false }: { compact?: boolean }) {
return ( return <section className={compact ? "" : "mx-auto w-full max-w-[1280px] px-5 py-12 sm:px-8"}><div className="rounded-lg border border-white/70 bg-background/86 p-3 shadow-[0_36px_100px_rgba(37,76,210,0.22)] backdrop-blur-xl"><div className="grid gap-3 lg:grid-cols-[1.06fr_0.94fr]"><div className="rounded-md border border-border bg-card p-5"><div className="mb-5 flex items-start justify-between gap-4"><div><div className="text-xs font-semibold uppercase tracking-[0.14em] text-primary">Issue radar</div><h2 className="mt-2 text-2xl font-semibold leading-tight">Платежи не проходят у части клиентов</h2></div><Badge className="rounded-md">Онлайн</Badge></div><div className="grid h-[260px] grid-cols-12 items-end gap-2 border-b border-l border-dashed border-border px-3 pb-3">{bars.map((bar, index) => <div key={index} className="flex items-end gap-1"><span className="w-full rounded-t-sm bg-primary" style={{ height: bar + "%" }} /><span className="w-full rounded-t-sm bg-accent" style={{ height: Math.max(16, bar - 24) + "%" }} /></div>)}</div><div className="mt-4 grid grid-cols-3 gap-2 text-xs text-muted-foreground"><div>affected 842</div><div>refund risk high</div><div>owner billing</div></div></div><div className="grid gap-3">{[["Затронутые диалоги", "842", "+27% за 2 часа"], ["Закрыто AI", "318", "без handoff"], ["QA warnings", "27", "требуют ревью"]].map(([label, value, meta]) => <div key={label} className="rounded-md border border-border bg-card p-5"><div className="text-sm text-muted-foreground">{label}</div><div className="mt-2 text-5xl font-semibold tracking-normal">{value}</div><div className="mt-3 text-xs font-semibold text-primary">{meta}</div></div>)}</div></div></div></section>;
<section className={compact ? "" : "mx-auto w-full max-w-[1180px] px-4 py-12 sm:px-6"}>
<div className="grid gap-3 lg:grid-cols-[1fr_360px]">
<div className="rounded-lg border border-border bg-card p-5">
<div className="mb-5 flex items-center justify-between">
<h2 className="font-display text-2xl font-semibold">Выявление проблем в реальном времени</h2>
<Badge className="rounded-md">Онлайн</Badge>
</div>
<div className="h-[260px]">
<ClientChart>
<ResponsiveContainer width="100%" height="100%" minWidth={1} minHeight={1}>
<BarChart data={revenueData}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--border)" />
<XAxis dataKey="name" tickLine={false} axisLine={false} />
<YAxis hide />
<Tooltip />
<Bar dataKey="value" radius={[6, 6, 0, 0]} fill="var(--primary)" />
<Bar dataKey="alt" radius={[6, 6, 0, 0]} fill="var(--accent)" />
</BarChart>
</ResponsiveContainer>
</ClientChart>
</div>
</div>
<div className="grid gap-3">
{["Затронутые диалоги", "Закрыто AI", "QA-предупреждения"].map((item, index) => (
<div key={item} className="rounded-lg border border-border bg-card p-5">
<div className="text-sm text-muted-foreground">{item}</div>
<div className="mt-2 font-display text-4xl font-semibold">{[842, 318, 27][index]}</div>
</div>
))}
</div>
</div>
</section>
);
} }
export function DashboardShell({ export function InnerHero({ eyebrow, title, text }: { eyebrow: string; title: string; text: string }) { return <section className="border-b border-border bg-card"><div className="mx-auto w-full max-w-[1280px] px-5 py-14 sm:px-8"><Badge variant="outline" className="mb-6 rounded-md border-primary/30 text-primary">{eyebrow}</Badge><h1 className="max-w-[960px] text-4xl font-semibold leading-[1.02] tracking-normal sm:text-6xl">{title}</h1><p className="mt-5 max-w-[760px] text-base leading-7 text-muted-foreground">{text}</p></div></section>; }
title,
description,
children,
}: {
title: string;
description: string;
children: React.ReactNode;
}) {
return (
<div className="mx-auto grid w-full max-w-[1440px] gap-0 md:grid-cols-[260px_1fr]">
<aside className="hidden min-h-[calc(100vh-4rem)] border-r border-border bg-card p-4 md:block">
<div className="mb-5 text-xs font-semibold uppercase text-muted-foreground">Операции</div>
<div className="grid gap-1">
{site.nav.map((item) => (
<Link key={item.href} href={item.href} className="rounded-md px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground">
{item.label}
</Link>
))}
</div>
</aside>
<section className="min-w-0">
<div className="border-b border-border bg-card px-4 py-6 sm:px-6">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<h1 className="font-display text-3xl font-semibold">{title}</h1>
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
</div>
<div className="flex gap-2">
<Button variant="outline" className="rounded-md"><BellIcon className="size-4" /> Алерты</Button>
<Button className="rounded-md">Экспорт</Button>
</div>
</div>
</div>
{children}
</section>
</div>
);
}
export function OpsDashboard() { export function IssueWorkflow() { const flow = [["01", "Сигнал", "topic volume растет быстрее нормы"], ["02", "Кластер", "AI связывает диалоги с общей причиной"], ["03", "Handoff", "оператор получает summary и next action"], ["04", "QA", "система проверяет ответ и тон"]] as const; return <section className="mx-auto w-full max-w-[1280px] px-5 py-14 sm:px-8"><div className="mb-8 max-w-[760px]"><div className="text-xs font-semibold uppercase tracking-[0.16em] text-primary">Workflow</div><h2 className="mt-2 text-4xl font-semibold leading-tight sm:text-6xl">От всплеска тикетов до корректного ответа</h2></div><div className="grid gap-3 md:grid-cols-4">{flow.map(([num, title, text]) => <article key={num} className="rounded-md border border-border bg-card p-5"><div className="mb-8 text-5xl font-semibold text-primary/75">{num}</div><h3 className="text-lg font-semibold">{title}</h3><p className="mt-3 text-sm leading-6 text-muted-foreground">{text}</p></article>)}</div></section>; }
return (
<DashboardShell title="Dashboard" description={site.tagline}>
<MetricStrip items={[
{ title: "Отправлено заказов", value: "1,248", text: "+18.2% к прошлой неделе", icon: CheckIcon },
{ title: "Поврежденные возвраты", value: "38", text: "-8.7% к прошлой неделе", icon: XIcon },
{ title: "Пропущенные интервалы", value: "27", text: "+4.3% к прошлой неделе", icon: BellIcon },
]} />
<OpsAnalytics />
<ShipmentTable />
</DashboardShell>
);
}
export function OpsAnalytics() { export function MetricStrip(_props: { items?: unknown } = {}) { return <section className="mx-auto grid w-full max-w-[1280px] gap-3 px-5 py-8 sm:px-8 md:grid-cols-3">{highlights.map((item) => { const Icon = item.icon; return <article key={item.title} className="rounded-md border border-border bg-card p-5"><Icon className="mb-6 size-5 text-primary" /><div className="text-4xl font-semibold">{item.value}</div><h3 className="mt-2 font-semibold">{item.title}</h3><p className="mt-2 text-sm leading-6 text-muted-foreground">{item.text}</p></article>; })}</section>; }
return (
<section className="mx-auto grid w-full max-w-[1180px] gap-3 px-4 py-8 sm:px-6 lg:grid-cols-[1fr_360px]">
<div className="rounded-lg border border-border bg-card p-5">
<h2 className="mb-5 font-display text-2xl font-semibold">Динамика выручки</h2>
<div className="h-[300px]">
<ClientChart>
<ResponsiveContainer width="100%" height="100%" minWidth={1} minHeight={1}>
<AreaChart data={revenueData}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--border)" />
<XAxis dataKey="name" tickLine={false} axisLine={false} />
<YAxis hide />
<Tooltip />
<Area type="monotone" dataKey="value" stroke="var(--primary)" fill="var(--primary)" fillOpacity={0.18} />
</AreaChart>
</ResponsiveContainer>
</ClientChart>
</div>
</div>
<div className="rounded-lg border border-border bg-card p-5">
<h2 className="mb-5 font-display text-2xl font-semibold">План выполнен</h2>
<div className="h-[220px]">
<ClientChart>
<ResponsiveContainer width="100%" height="100%" minWidth={1} minHeight={1}>
<PieChart>
<Pie data={pieData} innerRadius={58} outerRadius={88} dataKey="value">
{pieData.map((entry) => <Cell key={entry.name} fill={entry.fill} />)}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
</ClientChart>
</div>
<div className="text-center font-display text-4xl font-semibold">56%</div>
</div>
</section>
);
}
export function ShipmentTable() { export function FeaturedGrid({ eyebrow, title, text, items }: { eyebrow: string; title: string; text: string; items: readonly TextItem[] }) { return <section className="mx-auto w-full max-w-[1280px] px-5 py-14 sm:px-8"><div className="mb-8 grid gap-4 md:grid-cols-[0.8fr_1fr]"><div><div className="mb-2 text-xs font-semibold uppercase tracking-[0.16em] text-primary">{eyebrow}</div><h2 className="text-4xl font-semibold leading-tight sm:text-6xl">{title}</h2></div><p className="max-w-[620px] text-base leading-7 text-muted-foreground">{text}</p></div><div className="grid gap-3 md:grid-cols-4">{items.map((item) => <article key={item.name} className="rounded-md border border-border bg-card p-5"><Badge variant="secondary" className="mb-8 rounded-md">{item.tag}</Badge><div className="text-sm font-semibold text-primary">{item.price}</div><h3 className="mt-3 text-xl font-semibold">{item.name}</h3><p className="mt-3 text-sm leading-6 text-muted-foreground">{item.text}</p></article>)}</div></section>; }
const rows = [
["ORD-2048", "Out for delivery", "A17", "14:20", "В графике"],
["ORD-2049", "Packed", "WH-B", "16:10", "Готов"],
["RET-8831", "Inspection", "Returns", "11:40", "Риск"],
["ORD-2050", "Delayed", "A04", "18:30", "Действие"],
] as const;
return (
<section className="mx-auto w-full max-w-[1180px] px-4 py-8 sm:px-6">
<div className="overflow-x-auto rounded-lg border border-border bg-card">
<table className="w-full min-w-[720px] text-left text-sm">
<thead className="bg-muted text-muted-foreground">
<tr>
{["Заказ", "Статус", "Ответственный", "ETA", "Состояние"].map((head) => (
<th key={head} className="px-4 py-3 font-medium">{head}</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row) => (
<tr key={row[0]} className="border-t border-border">
{row.map((cell, index) => (
<td key={`${row[0]}-${cell}`} className={`px-4 py-3 ${index === 0 ? "font-semibold" : "text-muted-foreground"}`}>
{index === 4 ? <Badge variant={cell === "Риск" || cell === "Действие" ? "destructive" : "secondary"} className="rounded-md">{cell}</Badge> : cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</section>
);
}
export function RouteTimeline() { export function IntegrationStack() { const tools = ["Helpdesk", "CRM", "База знаний", "Биллинг", "Slack", "Email", "Чат", "Склад"] as const; return <section className="mx-auto grid w-full max-w-[1280px] gap-8 px-5 py-14 sm:px-8 lg:grid-cols-[0.75fr_1.25fr]"><div><div className="text-xs font-semibold uppercase tracking-[0.16em] text-primary">Integrations</div><h2 className="mt-2 text-4xl font-semibold leading-tight sm:text-5xl">AI не отвечает вслепую</h2><p className="mt-4 text-sm leading-7 text-muted-foreground">Шаблон показывает продуктовый слой интеграций без внешних вызовов, секретов и API.</p></div><div className="grid grid-cols-2 gap-3 sm:grid-cols-4">{tools.map((tool, index) => <div key={tool} className="rounded-md border border-border bg-card p-4"><div className="mb-7 flex size-9 items-center justify-center rounded-md bg-primary/10 text-xs font-semibold text-primary">{index + 1}</div><div className="font-semibold">{tool}</div></div>)}</div></section>; }
return (
<section className="mx-auto w-full max-w-[1180px] px-4 py-8 sm:px-6">
<div className="rounded-lg border border-border bg-card p-5">
<h2 className="mb-5 font-display text-2xl font-semibold">Таймлайн маршрута</h2>
<div className="grid gap-4">
{["Забор подтвержден", "Складское сканирование", "Курьер назначен", "Клиент уведомлен", "Доставка window"].map((step, index) => (
<div key={step} className="grid grid-cols-[32px_1fr_auto] items-center gap-3">
<span className="flex size-8 items-center justify-center rounded-md bg-primary text-xs font-semibold text-primary-foreground">{index + 1}</span>
<div>
<div className="font-medium">{step}</div>
<div className="text-xs text-muted-foreground">Route N-{204 + index}, checkpoint {index + 1}</div>
</div>
<Progress value={Math.min(100, 35 + index * 14)} className="w-24" />
</div>
))}
</div>
</div>
</section>
);
}
export function SettingsPanel() { export function IconCards({ items = eventTypes }: { items?: readonly IconItem[] }) { return <section className="mx-auto grid w-full max-w-[1280px] gap-3 px-5 py-12 sm:px-8 md:grid-cols-3">{items.map((item) => { const Icon = item.icon; return <article key={item.title} className="rounded-md border border-border bg-card p-6"><Icon className="mb-8 size-6 text-primary" /><h3 className="text-xl font-semibold">{item.title}</h3><p className="mt-3 text-sm leading-6 text-muted-foreground">{item.text}</p></article>; })}</section>; }
return (
<section className="mx-auto grid w-full max-w-[1180px] gap-3 px-4 py-8 sm:px-6 md:grid-cols-2">
<div className="rounded-lg border border-border bg-card p-5">
<h2 className="font-display text-2xl font-semibold">Правила уведомлений</h2>
<div className="mt-5 grid gap-3">
{["ETA drift > 15 min", "Damaged return created", "Carrier webhook delayed"].map((rule) => (
<div key={rule} className="flex items-center justify-between rounded-md border border-border p-3">
<span className="text-sm font-medium">{rule}</span>
<Badge className="rounded-md">Включено</Badge>
</div>
))}
</div>
</div>
<div className="rounded-lg border border-border bg-card p-5">
<h2 className="font-display text-2xl font-semibold">Рабочая область</h2>
<div className="mt-5 grid gap-3">
<Input value="FreightOps Moscow" readOnly />
<Input value="ops@freight.example" readOnly />
<Button className="rounded-md">Сохранить превью</Button>
</div>
</div>
</section>
);
}
export function PricingTiles({ title, items }: { title: string; items: readonly TileItem[] }) { return <section className="mx-auto w-full max-w-[1280px] px-5 py-14 sm:px-8"><h2 className="mb-7 text-4xl font-semibold sm:text-6xl">{title}</h2><div className="grid gap-4 md:grid-cols-3">{items.map((item, index) => <article key={item.title} className={index === 1 ? "rounded-md bg-foreground p-6 text-background" : "rounded-md border border-border bg-card p-6"}><h3 className="text-2xl font-semibold">{item.title}</h3><div className="mt-3 text-4xl font-semibold">{item.price}</div><Separator className="my-5" />{item.items.map((line) => <div key={line} className={index === 1 ? "mb-2 flex items-center gap-2 text-sm text-background/72" : "mb-2 flex items-center gap-2 text-sm text-muted-foreground"}><CheckIcon className="size-4" />{line}</div>)}</article>)}</div></section>; }
export function IssueWorkflow() { export function InfoColumns({ title, items }: { title: string; items: readonly { title: string; text: string }[] }) { return <section className="mx-auto w-full max-w-[1280px] px-5 py-12 sm:px-8"><h2 className="mb-6 text-4xl font-semibold">{title}</h2><div className="grid gap-3 md:grid-cols-3">{items.map((item) => <article key={item.title} className="rounded-md border border-border bg-card p-6"><ShieldCheckIcon className="mb-8 size-5 text-primary" /><h3 className="text-xl font-semibold">{item.title}</h3><p className="mt-3 text-sm leading-6 text-muted-foreground">{item.text}</p></article>)}</div></section>; }
const flow = [
["01", "Сигнал", "Рост темы «оплата не проходит» замечен за 30 секунд."],
["02", "Гипотеза", "AI группирует затронутые диалоги и показывает общий pattern."],
["03", "Handoff", "Оператор получает summary, риск SLA и готовый ответ клиенту."],
["04", "QA", "После закрытия система проверяет тон, полноту и compliance."],
] as const;
return ( export function SplitStory({ image, eyebrow, title, text, points }: { image: string; eyebrow: string; title: string; text: string; points: readonly string[] }) { return <section className="mx-auto grid w-full max-w-[1280px] gap-10 px-5 py-14 sm:px-8 lg:grid-cols-2"><div className="relative min-h-[430px] overflow-hidden rounded-md border border-border bg-muted"><Image src={image} alt={title} fill className="object-cover" sizes="(min-width: 1024px) 50vw, 100vw" /></div><div className="flex flex-col justify-center"><div className="mb-3 text-xs font-semibold uppercase tracking-[0.16em] text-primary">{eyebrow}</div><h2 className="text-4xl font-semibold leading-tight sm:text-5xl">{title}</h2><p className="mt-5 text-base leading-7 text-muted-foreground">{text}</p><div className="mt-7 grid gap-3">{points.map((point) => <div key={point} className="flex items-center gap-3 text-sm font-semibold"><CheckIcon className="size-4 text-primary" />{point}</div>)}</div></div></section>; }
<section className="mx-auto w-full max-w-[1180px] px-4 py-12 sm:px-6">
<div className="mb-8 max-w-[760px]">
<div className="text-xs font-semibold uppercase text-primary">Agent workflow</div>
<h2 className="mt-2 font-display text-4xl font-semibold leading-tight sm:text-6xl">От всплеска тикетов до корректного ответа за один рабочий поток</h2>
</div>
<div className="grid gap-3 md:grid-cols-4">
{flow.map(([num, title, text]) => (
<article key={num} className="rounded-lg border border-border bg-card p-5">
<div className="mb-8 font-display text-5xl font-semibold text-primary/70">{num}</div>
<h3 className="text-lg font-semibold">{title}</h3>
<p className="mt-3 text-sm leading-6 text-muted-foreground">{text}</p>
</article>
))}
</div>
</section>
);
}
export function IntegrationStack() { export function TestimonialBand(_props: { items?: unknown } = {}) { return <section className="mx-auto grid w-full max-w-[1280px] gap-4 px-5 py-12 sm:px-8 md:grid-cols-2">{testimonials.map((item) => <blockquote key={item.name} className="rounded-md border border-border bg-card p-7"><div className="text-sm font-semibold text-primary">{item.rating}</div><p className="mt-5 text-2xl font-semibold leading-snug">{item.text}</p><footer className="mt-5 text-sm text-muted-foreground">{item.name}</footer></blockquote>)}</section>; }
const tools = ["Helpdesk", "CRM", "Docs", "Billing", "Slack", "Email", "Chat", "Warehouse"] as const;
return (
<section className="mx-auto grid w-full max-w-[1180px] gap-8 px-4 py-12 sm:px-6 lg:grid-cols-[0.8fr_1.2fr]">
<div>
<h2 className="font-display text-4xl font-semibold leading-tight">Интеграции показаны как слой продукта, а не список логотипов</h2>
<p className="mt-4 text-sm leading-7 text-muted-foreground">Шаблон оставляет место под реальные коннекторы, но не делает внешних вызовов и не хранит секреты.</p>
</div>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
{tools.map((tool) => (
<div key={tool} className="rounded-lg border border-border bg-card p-4 text-sm font-semibold">
<div className="mb-5 size-8 rounded-md bg-primary/12" />
{tool}
</div>
))}
</div>
</section>
);
}
export function CtaPanel({ title, text, href, label }: { title: string; text: string; href: string; label: string }) { return <section className="mx-auto w-full max-w-[1280px] px-5 py-12 sm:px-8"><div className="rounded-md bg-primary p-8 text-primary-foreground"><SparklesIcon className="mb-8 size-6" /><h2 className="text-4xl font-semibold">{title}</h2><p className="mt-4 max-w-[720px] text-sm leading-7 text-primary-foreground/75">{text}</p><Button asChild variant="secondary" className="mt-6 rounded-md"><Link href={href}>{label}<ArrowRightIcon className="size-4" /></Link></Button></div></section>; }