feat: split template file and update agents.md
This commit is contained in:
57
AGENTS.md
57
AGENTS.md
@@ -5,7 +5,7 @@ SignalDesk AI — B2B SaaS для поддержки; сохраняй launch-pa
|
|||||||
## Project Specifics
|
## Project Specifics
|
||||||
|
|
||||||
- Source content lives in `src/entities/site-content.ts`; keep visible copy there or directly in route JSX.
|
- Source content lives in `src/entities/site-content.ts`; keep visible copy there or directly in route JSX.
|
||||||
- `src/app` is routing only; page composition belongs to `src/widgets/template-ui.tsx`.
|
- `src/app` is routing only; композиция каждой страницы живёт в отдельном widget (`src/widgets/<page>-page.tsx`). См. File Map.
|
||||||
- Keep cards at 8px radius or less and preserve the selected palette in `src/app/globals.css`.
|
- Keep cards at 8px radius or less and preserve the selected palette in `src/app/globals.css`.
|
||||||
- This is a static frontend template: do not add real payments, auth, persistence, external API calls, or backend contracts without an explicit product request.
|
- This is a static frontend template: do not add real payments, auth, persistence, external API calls, or backend contracts without an explicit product request.
|
||||||
- After changes run `pnpm lint` and `pnpm build`.
|
- After changes run `pnpm lint` and `pnpm build`.
|
||||||
@@ -14,3 +14,58 @@ SignalDesk AI — B2B SaaS для поддержки; сохраняй launch-pa
|
|||||||
|
|
||||||
- Кириллица обязательна для видимого текста и выбранных Google Fonts.
|
- Кириллица обязательна для видимого текста и выбранных Google Fonts.
|
||||||
- При AI-правках сохраняйте доменные блоки этого шаблона: они отличают проект от generic landing.
|
- При AI-правках сохраняйте доменные блоки этого шаблона: они отличают проект от generic landing.
|
||||||
|
|
||||||
|
## Design System
|
||||||
|
|
||||||
|
Источник токенов — `src/app/globals.css` (`@theme inline` + `:root`/`.dark`). Шрифты: заголовки (`h1/h2/h3`, `.font-display`) — **Roboto Flex** (`--font-display`), текст/UI — **Inter** (`--font-sans`); mono в теме указывает на тот же display. Работай через семантические классы Tailwind (`bg-primary`, `text-foreground`, `border-border`, `text-muted-foreground`), никогда не хардкодь hex/oklch.
|
||||||
|
|
||||||
|
Личность: **clean B2B SaaS launch-page** — почти белый фон (`oklch(0.985 0 0)`), глубокий cobalt/indigo primary как единственный фирменный цвет, бирюзовый teal accent для данных и диаграмм, мягкие нейтральные границы. Воздух, плотная сетка `max-w-[1280px]`, крупные семиболдовые заголовки. Не «громкий», а спокойный и доверительный.
|
||||||
|
|
||||||
|
| Роль | Light | Характер |
|
||||||
|
|---|---|---|
|
||||||
|
| `background` | почти белый (`oklch 0.985`) | основной фон страниц |
|
||||||
|
| `foreground` | near-black indigo-ink | текст, тёмные CTA-плашки (`bg-foreground text-background`) |
|
||||||
|
| `primary` | глубокий cobalt/indigo | бренд-акцент: лого, ссылки, активные числа, primary CTA |
|
||||||
|
| `accent` | teal/бирюза | вторые столбцы диаграмм, точечные акценты данных |
|
||||||
|
| `secondary` | холодный светло-серый | мягкие бейджи, фоновые плашки |
|
||||||
|
| `muted` / `muted-foreground` | светло-серый / серо-синий | вторичный текст, подписи |
|
||||||
|
| `card` | белый (`oklch 1`) | карточки и секции на фоне |
|
||||||
|
|
||||||
|
Узнаваемые приёмы (держи их — это «лицо» шаблона):
|
||||||
|
- **Мягкие, не острые углы:** `--radius` = 0.5rem; карточки и кнопки почти всегда `rounded-md` (8px и меньше). Не уходи в `rounded-none` или сильно скруглённые pill-формы.
|
||||||
|
- **Тонкие нейтральные границы:** `border border-border` (1px), без толстых брутализм-рамок.
|
||||||
|
- **Hero-фоны на CSS-утилитах:** `.signal-aurora` (анимированный radial-градиент cobalt/teal/lime) + `.signal-grid` (точечная сетка с mask-fade). Используй их для launch hero вместо плоского цвета. Есть также `.grainient-field`.
|
||||||
|
- **Глубокие «продуктовые» тени для dashboard-панели:** `shadow-[0_36px_100px_rgba(37,76,210,0.22)]` + `backdrop-blur-xl` на demo-панели.
|
||||||
|
- **Типографика:** `font-semibold`, крупные размеры (`text-4xl`…`text-7xl`), плотный `leading`, eyebrow-метки `text-xs font-semibold uppercase tracking-[0.16em] text-primary`.
|
||||||
|
|
||||||
|
Do / Don't:
|
||||||
|
- **Do:** держи cobalt primary единственным брендовым цветом; data/charts подсвечивай `accent`; используй eyebrow + крупный заголовок + сетку карточек; demo dashboard оставляй mock без реальных API.
|
||||||
|
- **Don't:** не добавляй второй яркий бренд-цвет, неоновые градиенты на тексте, толстые рамки, острые `rounded-none` углы или тяжёлый брутализм — это ломает спокойный SaaS-тон.
|
||||||
|
|
||||||
|
## File Map
|
||||||
|
|
||||||
|
| Route | Widget |
|
||||||
|
|---|---|
|
||||||
|
| `/` | `src/widgets/home-page.tsx` (`HomePage`) |
|
||||||
|
| `/product` | `src/widgets/product-page.tsx` (`ProductPage`) |
|
||||||
|
| `/pricing` | `src/widgets/pricing-page.tsx` (`PricingPage`) |
|
||||||
|
| `/customers` | `src/widgets/customers-page.tsx` (`CustomersPage`) |
|
||||||
|
| `/resources` | `src/widgets/resources-page.tsx` (`ResourcesPage`) |
|
||||||
|
| `/dashboard` | `src/widgets/dashboard-page.tsx` (`DashboardPage`) |
|
||||||
|
|
||||||
|
Переиспользуемые блоки:
|
||||||
|
- `src/widgets/site-shell.tsx` — `SiteHeader` + `SiteFooter` (обёртка из `src/app/layout.tsx`).
|
||||||
|
- `src/widgets/saas-dashboard.tsx` — `SaasDashboard` (Home hero, Dashboard).
|
||||||
|
- `src/widgets/issue-workflow.tsx` — `IssueWorkflow` (Home, Dashboard, Product).
|
||||||
|
- `src/widgets/integration-stack.tsx` — `IntegrationStack` (Home, Product).
|
||||||
|
- `src/widgets/metric-strip.tsx` — `MetricStrip` (Home; рендерит `highlights`).
|
||||||
|
- `src/widgets/testimonial-band.tsx` — `TestimonialBand` (Home, Customers; рендерит `testimonials`).
|
||||||
|
- `src/shared/ui/inner-hero.tsx` — `InnerHero` (хедер всех внутренних страниц).
|
||||||
|
- `src/shared/ui/featured-grid.tsx` — `FeaturedGrid` (Home, Product, Resources).
|
||||||
|
- `src/shared/ui/icon-cards.tsx` — `IconCards` (Dashboard, Product).
|
||||||
|
- `src/shared/ui/pricing-tiles.tsx` — `PricingTiles` (Pricing).
|
||||||
|
- `src/shared/ui/info-columns.tsx` — `InfoColumns` (Pricing, Resources).
|
||||||
|
- `src/shared/ui/split-story.tsx` — `SplitStory` (Home, Customers).
|
||||||
|
- `src/shared/ui/cta-panel.tsx` — `CtaPanel` (Product).
|
||||||
|
|
||||||
|
Одноразовые блоки колоцированы со своей страницей: `GrainientHero` (launch hero) живёт внутри `src/widgets/home-page.tsx`.
|
||||||
|
|||||||
@@ -1,15 +1,5 @@
|
|||||||
"use client";
|
import { CustomersPage } from "@/widgets/customers-page";
|
||||||
|
|
||||||
import { InnerHero, SplitStory, TestimonialBand } from "@/widgets/template-ui";
|
|
||||||
import { testimonials } from "@/entities/site-content";
|
|
||||||
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return <CustomersPage />;
|
||||||
<>
|
|
||||||
<InnerHero eyebrow="Customers" title="Команды поддержки используют SignalDesk как radar, QA и agent assist" text="Customer page продает outcome: меньше эскалаций, быстрее реакция, выше качество." />
|
|
||||||
<TestimonialBand items={testimonials} />
|
|
||||||
<SplitStory image="https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&w=1100&q=80" eyebrow="Case study" title="MintCart нашел корень refund wave за 14 минут" text="Issue radar связал всплеск тикетов с failed payment retries и помог support/product командам закрыть проблему." points={["-38% escalations", "+24% faster first response", "100% QA coverage"]} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
"use client";
|
import { DashboardPage } from "@/widgets/dashboard-page";
|
||||||
|
|
||||||
import { IconCards, InnerHero, IssueWorkflow, SaasDashboard } from "@/widgets/template-ui";
|
|
||||||
import { contactCards } from "@/entities/site-content";
|
|
||||||
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return <DashboardPage />;
|
||||||
<>
|
|
||||||
<InnerHero eyebrow="Dashboard" title="Demo dashboard для очереди, SLA и topic volume" text="Показывает, как может выглядеть продуктовый интерфейс внутри SaaS-шаблона." />
|
|
||||||
<SaasDashboard />
|
|
||||||
<IssueWorkflow />
|
|
||||||
<IconCards items={contactCards} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Inter, Roboto_Flex } from "next/font/google";
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { ThemeProvider } from "@/shared/hooks/theme-provider";
|
import { ThemeProvider } from "@/shared/hooks/theme-provider";
|
||||||
import { ThemeMessageListener } from "@/shared/hooks/theme-message-listener";
|
import { ThemeMessageListener } from "@/shared/hooks/theme-message-listener";
|
||||||
import { SiteHeader, SiteFooter } from "@/widgets/template-ui";
|
import { SiteHeader, SiteFooter } from "@/widgets/site-shell";
|
||||||
|
|
||||||
const display = Roboto_Flex({
|
const display = Roboto_Flex({
|
||||||
variable: "--font-display",
|
variable: "--font-display",
|
||||||
|
|||||||
@@ -1,19 +1,5 @@
|
|||||||
"use client";
|
import { HomePage } from "@/widgets/home-page";
|
||||||
|
|
||||||
import { FeaturedGrid, GrainientHero, IntegrationStack, IssueWorkflow, MetricStrip, SplitStory, TestimonialBand } from "@/widgets/template-ui";
|
|
||||||
import { highlights, products, site, testimonials } from "@/entities/site-content";
|
|
||||||
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return <HomePage />;
|
||||||
<>
|
|
||||||
<GrainientHero />
|
|
||||||
<IssueWorkflow />
|
|
||||||
<MetricStrip items={highlights} />
|
|
||||||
<FeaturedGrid eyebrow="Platform" title="AI support system, а не просто чат-бот" text="Блоки покрывают ключевые SaaS-сообщения: automation, detection, QA, analytics и business outcomes." items={products} />
|
|
||||||
<SplitStory image={site.accentImage} eyebrow="Workflow" title="От всплеска проблемы до корректного handoff" text="SignalDesk собирает сигналы из очереди, группирует темы и помогает команде реагировать до того, как SLA провален." points={["Real-time issue detection", "Agent assist", "QA analytics"]} />
|
|
||||||
<IntegrationStack />
|
|
||||||
<TestimonialBand items={testimonials} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,5 @@
|
|||||||
"use client";
|
import { PricingPage } from "@/widgets/pricing-page";
|
||||||
|
|
||||||
import { InfoColumns, InnerHero, PricingTiles } from "@/widgets/template-ui";
|
|
||||||
import { tastingSets } from "@/entities/site-content";
|
|
||||||
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return <PricingPage />;
|
||||||
<>
|
|
||||||
<InnerHero eyebrow="Pricing" title="Простые тарифы для команд поддержки разных размеров" text="Страница pricing готова для SaaS: tiers, seats, modules и FAQ." />
|
|
||||||
<PricingTiles title="Plans" items={tastingSets} />
|
|
||||||
<InfoColumns title="Что входит" items={[{ title: "AI actions", text: "Черновики, summaries, classification и QA checks." }, { title: "Seat model", text: "Операторы, тимлиды и администраторы в одной workspace-модели." }, { title: "Security", text: "SSO и policy controls вынесены в Scale." }]} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,5 @@
|
|||||||
"use client";
|
import { ProductPage } from "@/widgets/product-page";
|
||||||
|
|
||||||
import { CtaPanel, FeaturedGrid, IconCards, InnerHero, IntegrationStack, IssueWorkflow } from "@/widgets/template-ui";
|
|
||||||
import { eventTypes, products } from "@/entities/site-content";
|
|
||||||
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return <ProductPage />;
|
||||||
<>
|
|
||||||
<InnerHero eyebrow="Product" title="Все, что нужно support-команде после запуска AI" text="Product page раскрывает платформу по слоям: inbox, agent workflow, issue radar, QA и reporting." />
|
|
||||||
<IssueWorkflow />
|
|
||||||
<IconCards items={eventTypes} />
|
|
||||||
<FeaturedGrid eyebrow="Capabilities" title="Core modules" text="Каждый модуль описан как самостоятельная ценность для B2B-покупателя." items={products} />
|
|
||||||
<IntegrationStack />
|
|
||||||
<CtaPanel title="Покажите SignalDesk на своей очереди" text="CTA ведет на demo/dashboard без реальных внешних интеграций." href="/dashboard" label="Открыть демо-панель" />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,5 @@
|
|||||||
"use client";
|
import { ResourcesPage } from "@/widgets/resources-page";
|
||||||
|
|
||||||
import { FeaturedGrid, InfoColumns, InnerHero } from "@/widgets/template-ui";
|
|
||||||
import { products } from "@/entities/site-content";
|
|
||||||
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return <ResourcesPage />;
|
||||||
<>
|
|
||||||
<InnerHero eyebrow="Resources" title="Guides, playbooks и launch notes для AI support" text="Страница контента помогает SaaS продавать экспертизу, а не только продукт." />
|
|
||||||
<FeaturedGrid eyebrow="Latest" title="Insights for support builders" text="Cards подходят для blog/resources без CMS." items={products} />
|
|
||||||
<InfoColumns title="Playbooks" items={[{ title: "AI handoff", text: "Когда агент должен остановиться и передать человеку." }, { title: "QA scorecards", text: "Какие сигналы качества проверять автоматически." }, { title: "Topic spikes", text: "Как реагировать на всплески проблем в реальном времени." }]} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/shared/ui/cta-panel.tsx
Normal file
6
src/shared/ui/cta-panel.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { ArrowRightIcon, SparklesIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import { Button } from "@/shared/ui/button";
|
||||||
|
|
||||||
|
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>; }
|
||||||
5
src/shared/ui/featured-grid.tsx
Normal file
5
src/shared/ui/featured-grid.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { Badge } from "@/shared/ui/badge";
|
||||||
|
|
||||||
|
type TextItem = { name: string; price: string; tag: string; text: string };
|
||||||
|
|
||||||
|
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>; }
|
||||||
6
src/shared/ui/icon-cards.tsx
Normal file
6
src/shared/ui/icon-cards.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { eventTypes } from "@/entities/site-content";
|
||||||
|
|
||||||
|
type IconComponent = React.ComponentType<{ className?: string }>;
|
||||||
|
type IconItem = { title: string; text: string; icon: IconComponent };
|
||||||
|
|
||||||
|
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>; }
|
||||||
3
src/shared/ui/info-columns.tsx
Normal file
3
src/shared/ui/info-columns.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { ShieldCheckIcon } from "lucide-react";
|
||||||
|
|
||||||
|
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>; }
|
||||||
3
src/shared/ui/inner-hero.tsx
Normal file
3
src/shared/ui/inner-hero.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { Badge } from "@/shared/ui/badge";
|
||||||
|
|
||||||
|
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>; }
|
||||||
7
src/shared/ui/pricing-tiles.tsx
Normal file
7
src/shared/ui/pricing-tiles.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { CheckIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import { Separator } from "@/shared/ui/separator";
|
||||||
|
|
||||||
|
type TileItem = { title: string; price: string; items: readonly string[] };
|
||||||
|
|
||||||
|
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>; }
|
||||||
4
src/shared/ui/split-story.tsx
Normal file
4
src/shared/ui/split-story.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import { CheckIcon } from "lucide-react";
|
||||||
|
|
||||||
|
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>; }
|
||||||
14
src/widgets/customers-page.tsx
Normal file
14
src/widgets/customers-page.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { testimonials } from "@/entities/site-content";
|
||||||
|
import { InnerHero } from "@/shared/ui/inner-hero";
|
||||||
|
import { SplitStory } from "@/shared/ui/split-story";
|
||||||
|
import { TestimonialBand } from "@/widgets/testimonial-band";
|
||||||
|
|
||||||
|
export function CustomersPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InnerHero eyebrow="Customers" title="Команды поддержки используют SignalDesk как radar, QA и agent assist" text="Customer page продает outcome: меньше эскалаций, быстрее реакция, выше качество." />
|
||||||
|
<TestimonialBand items={testimonials} />
|
||||||
|
<SplitStory image="https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&w=1100&q=80" eyebrow="Case study" title="MintCart нашел корень refund wave за 14 минут" text="Issue radar связал всплеск тикетов с failed payment retries и помог support/product командам закрыть проблему." points={["-38% escalations", "+24% faster first response", "100% QA coverage"]} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
src/widgets/dashboard-page.tsx
Normal file
16
src/widgets/dashboard-page.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { contactCards } from "@/entities/site-content";
|
||||||
|
import { IconCards } from "@/shared/ui/icon-cards";
|
||||||
|
import { InnerHero } from "@/shared/ui/inner-hero";
|
||||||
|
import { IssueWorkflow } from "@/widgets/issue-workflow";
|
||||||
|
import { SaasDashboard } from "@/widgets/saas-dashboard";
|
||||||
|
|
||||||
|
export function DashboardPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InnerHero eyebrow="Dashboard" title="Demo dashboard для очереди, SLA и topic volume" text="Показывает, как может выглядеть продуктовый интерфейс внутри SaaS-шаблона." />
|
||||||
|
<SaasDashboard />
|
||||||
|
<IssueWorkflow />
|
||||||
|
<IconCards items={contactCards} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
31
src/widgets/home-page.tsx
Normal file
31
src/widgets/home-page.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { ArrowRightIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import { highlights, products, site, testimonials } from "@/entities/site-content";
|
||||||
|
import { Badge } from "@/shared/ui/badge";
|
||||||
|
import { Button } from "@/shared/ui/button";
|
||||||
|
import { FeaturedGrid } from "@/shared/ui/featured-grid";
|
||||||
|
import { SplitStory } from "@/shared/ui/split-story";
|
||||||
|
import { IntegrationStack } from "@/widgets/integration-stack";
|
||||||
|
import { IssueWorkflow } from "@/widgets/issue-workflow";
|
||||||
|
import { MetricStrip } from "@/widgets/metric-strip";
|
||||||
|
import { SaasDashboard } from "@/widgets/saas-dashboard";
|
||||||
|
import { TestimonialBand } from "@/widgets/testimonial-band";
|
||||||
|
|
||||||
|
function GrainientHero() {
|
||||||
|
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>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HomePage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<GrainientHero />
|
||||||
|
<IssueWorkflow />
|
||||||
|
<MetricStrip items={highlights} />
|
||||||
|
<FeaturedGrid eyebrow="Platform" title="AI support system, а не просто чат-бот" text="Блоки покрывают ключевые SaaS-сообщения: automation, detection, QA, analytics и business outcomes." items={products} />
|
||||||
|
<SplitStory image={site.accentImage} eyebrow="Workflow" title="От всплеска проблемы до корректного handoff" text="SignalDesk собирает сигналы из очереди, группирует темы и помогает команде реагировать до того, как SLA провален." points={["Real-time issue detection", "Agent assist", "QA analytics"]} />
|
||||||
|
<IntegrationStack />
|
||||||
|
<TestimonialBand items={testimonials} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
src/widgets/integration-stack.tsx
Normal file
1
src/widgets/integration-stack.tsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
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>; }
|
||||||
1
src/widgets/issue-workflow.tsx
Normal file
1
src/widgets/issue-workflow.tsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
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>; }
|
||||||
3
src/widgets/metric-strip.tsx
Normal file
3
src/widgets/metric-strip.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { highlights } from "@/entities/site-content";
|
||||||
|
|
||||||
|
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>; }
|
||||||
14
src/widgets/pricing-page.tsx
Normal file
14
src/widgets/pricing-page.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { tastingSets } from "@/entities/site-content";
|
||||||
|
import { InfoColumns } from "@/shared/ui/info-columns";
|
||||||
|
import { InnerHero } from "@/shared/ui/inner-hero";
|
||||||
|
import { PricingTiles } from "@/shared/ui/pricing-tiles";
|
||||||
|
|
||||||
|
export function PricingPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InnerHero eyebrow="Pricing" title="Простые тарифы для команд поддержки разных размеров" text="Страница pricing готова для SaaS: tiers, seats, modules и FAQ." />
|
||||||
|
<PricingTiles title="Plans" items={tastingSets} />
|
||||||
|
<InfoColumns title="Что входит" items={[{ title: "AI actions", text: "Черновики, summaries, classification и QA checks." }, { title: "Seat model", text: "Операторы, тимлиды и администраторы в одной workspace-модели." }, { title: "Security", text: "SSO и policy controls вынесены в Scale." }]} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
src/widgets/product-page.tsx
Normal file
20
src/widgets/product-page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { eventTypes, products } from "@/entities/site-content";
|
||||||
|
import { CtaPanel } from "@/shared/ui/cta-panel";
|
||||||
|
import { FeaturedGrid } from "@/shared/ui/featured-grid";
|
||||||
|
import { IconCards } from "@/shared/ui/icon-cards";
|
||||||
|
import { InnerHero } from "@/shared/ui/inner-hero";
|
||||||
|
import { IntegrationStack } from "@/widgets/integration-stack";
|
||||||
|
import { IssueWorkflow } from "@/widgets/issue-workflow";
|
||||||
|
|
||||||
|
export function ProductPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InnerHero eyebrow="Product" title="Все, что нужно support-команде после запуска AI" text="Product page раскрывает платформу по слоям: inbox, agent workflow, issue radar, QA и reporting." />
|
||||||
|
<IssueWorkflow />
|
||||||
|
<IconCards items={eventTypes} />
|
||||||
|
<FeaturedGrid eyebrow="Capabilities" title="Core modules" text="Каждый модуль описан как самостоятельная ценность для B2B-покупателя." items={products} />
|
||||||
|
<IntegrationStack />
|
||||||
|
<CtaPanel title="Покажите SignalDesk на своей очереди" text="CTA ведет на demo/dashboard без реальных внешних интеграций." href="/dashboard" label="Открыть демо-панель" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
src/widgets/resources-page.tsx
Normal file
14
src/widgets/resources-page.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { products } from "@/entities/site-content";
|
||||||
|
import { FeaturedGrid } from "@/shared/ui/featured-grid";
|
||||||
|
import { InfoColumns } from "@/shared/ui/info-columns";
|
||||||
|
import { InnerHero } from "@/shared/ui/inner-hero";
|
||||||
|
|
||||||
|
export function ResourcesPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InnerHero eyebrow="Resources" title="Guides, playbooks и launch notes для AI support" text="Страница контента помогает SaaS продавать экспертизу, а не только продукт." />
|
||||||
|
<FeaturedGrid eyebrow="Latest" title="Insights for support builders" text="Cards подходят для blog/resources без CMS." items={products} />
|
||||||
|
<InfoColumns title="Playbooks" items={[{ title: "AI handoff", text: "Когда агент должен остановиться и передать человеку." }, { title: "QA scorecards", text: "Какие сигналы качества проверять автоматически." }, { title: "Topic spikes", text: "Как реагировать на всплески проблем в реальном времени." }]} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
src/widgets/saas-dashboard.tsx
Normal file
7
src/widgets/saas-dashboard.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Badge } from "@/shared/ui/badge";
|
||||||
|
|
||||||
|
const bars = [18, 26, 22, 42, 58, 48, 72, 80, 62, 88, 96, 76];
|
||||||
|
|
||||||
|
export function SaasDashboard({ compact = false }: { compact?: boolean }) {
|
||||||
|
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>;
|
||||||
|
}
|
||||||
11
src/widgets/site-shell.tsx
Normal file
11
src/widgets/site-shell.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { MenuIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import { site } from "@/entities/site-content";
|
||||||
|
import { Button } from "@/shared/ui/button";
|
||||||
|
|
||||||
|
export function SiteHeader() {
|
||||||
|
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>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>; }
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import Image from "next/image";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { ArrowRightIcon, CheckIcon, MenuIcon, ShieldCheckIcon, SparklesIcon } from "lucide-react";
|
|
||||||
|
|
||||||
import { eventTypes, highlights, site, testimonials } from "@/entities/site-content";
|
|
||||||
import { Badge } from "@/shared/ui/badge";
|
|
||||||
import { Button } from "@/shared/ui/button";
|
|
||||||
import { Separator } from "@/shared/ui/separator";
|
|
||||||
|
|
||||||
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 };
|
|
||||||
|
|
||||||
const bars = [18, 26, 22, 42, 58, 48, 72, 80, 62, 88, 96, 76];
|
|
||||||
|
|
||||||
export function SiteHeader() {
|
|
||||||
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>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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>; }
|
|
||||||
|
|
||||||
export function GrainientHero() {
|
|
||||||
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>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SaasDashboard({ compact = false }: { compact?: boolean }) {
|
|
||||||
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>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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>; }
|
|
||||||
|
|
||||||
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>; }
|
|
||||||
|
|
||||||
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>; }
|
|
||||||
|
|
||||||
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>; }
|
|
||||||
|
|
||||||
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>; }
|
|
||||||
|
|
||||||
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>; }
|
|
||||||
|
|
||||||
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 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>; }
|
|
||||||
|
|
||||||
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>; }
|
|
||||||
|
|
||||||
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>; }
|
|
||||||
|
|
||||||
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>; }
|
|
||||||
3
src/widgets/testimonial-band.tsx
Normal file
3
src/widgets/testimonial-band.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { testimonials } from "@/entities/site-content";
|
||||||
|
|
||||||
|
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>; }
|
||||||
Reference in New Issue
Block a user