first commit

This commit is contained in:
grigorij
2026-08-03 21:02:34 +04:00
commit b0bafe740f
39 changed files with 2326 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
import { NextRequest, NextResponse } from "next/server";
export const runtime = "nodejs";
const MAX_LENGTHS = {
name: 100,
contact: 150,
message: 2000,
};
// Убираем HTML-теги и обрезаем длину, чтобы нельзя было сломать
// форматирование сообщения в Telegram или протащить лишний код.
function sanitize(value: unknown, maxLength: number): string {
if (typeof value !== "string") return "";
return value.replace(/<[^>]*>/g, "").trim().slice(0, maxLength);
}
// Экранируем спецсимволы Telegram MarkdownV2.
function escapeMarkdown(text: string): string {
return text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, (m) => `\\${m}`);
}
export async function POST(req: NextRequest) {
const token = process.env.TELEGRAM_BOT_TOKEN;
const chatId = process.env.TELEGRAM_CHAT_ID;
if (!token || !chatId) {
console.error("TELEGRAM_BOT_TOKEN или TELEGRAM_CHAT_ID не заданы");
return NextResponse.json(
{ ok: false, error: "Сервис временно недоступен, попробуйте позже" },
{ status: 500 }
);
}
let body: unknown;
try {
body = await req.json();
} catch {
return NextResponse.json(
{ ok: false, error: "Некорректный запрос" },
{ status: 400 }
);
}
const { name, contact, message } = (body ?? {}) as Record<string, unknown>;
const cleanName = sanitize(name, MAX_LENGTHS.name);
const cleanContact = sanitize(contact, MAX_LENGTHS.contact);
const cleanMessage = sanitize(message, MAX_LENGTHS.message);
if (!cleanName || !cleanContact || !cleanMessage) {
return NextResponse.json(
{ ok: false, error: "Заполните имя, контакт и сообщение" },
{ status: 400 }
);
}
const text = [
"*Новая заявка с сайта LabNetHub*",
"",
`*Имя:* ${escapeMarkdown(cleanName)}`,
`*Контакт:* ${escapeMarkdown(cleanContact)}`,
`*Сообщение:*`,
escapeMarkdown(cleanMessage),
].join("\n");
try {
const telegramRes = await fetch(
`https://api.telegram.org/bot${token}/sendMessage`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: chatId,
text,
parse_mode: "MarkdownV2",
}),
}
);
if (!telegramRes.ok) {
const details = await telegramRes.text();
console.error("Telegram API error:", details);
return NextResponse.json(
{ ok: false, error: "Не удалось отправить сообщение" },
{ status: 502 }
);
}
return NextResponse.json({ ok: true });
} catch (err) {
console.error("Ошибка отправки в Telegram:", err);
return NextResponse.json(
{ ok: false, error: "Не удалось отправить сообщение" },
{ status: 500 }
);
}
}
+150
View File
@@ -0,0 +1,150 @@
import type { Metadata } from "next";
import Link from "next/link";
import {
Send,
MessageCircle,
ArrowUpRight,
Check,
Bell,
ShoppingBag,
Headphones,
Workflow,
Mail,
FileText,
} from "lucide-react";
import PageHero from "@/components/PageHero";
import SectionEyebrow from "@/components/SectionEyebrow";
import CTASection from "@/components/CTASection";
export const metadata: Metadata = {
title: "Боты для Telegram и VK",
description:
"Разработка Telegram- и VK-ботов любой сложности: автоматизация заявок, уведомления, интеграция с CRM.",
};
const platforms = [
{
icon: Send,
title: "Telegram-боты",
description:
"Боты любой сложности: приём заявок, уведомления, интеграция с CRM и сервисами.",
points: ["Диалоговые сценарии и кнопки", "Оплата и интеграции", "Уведомления в реальном времени"],
},
{
icon: MessageCircle,
title: "Боты для ВКонтакте",
description:
"Боты для сообществ VK: рассылки, магазины в чате, поддержка подписчиков.",
points: ["Автоответы и сценарии диалога", "Магазин прямо в сообществе", "Рассылки по базе подписчиков"],
},
];
const useCases = [
{
icon: FileText,
title: "Приём заявок",
description: "Клиент оставляет заявку в чате — она сразу попадает в CRM или менеджеру.",
},
{
icon: Bell,
title: "Уведомления",
description: "Автоматические напоминания клиентам и сотрудникам о статусе заказа.",
},
{
icon: ShoppingBag,
title: "Магазин в чате",
description: "Каталог, корзина и оформление заказа без перехода на сайт.",
},
{
icon: Headphones,
title: "Поддержка клиентов",
description: "Ответы на частые вопросы и передача сложных случаев менеджеру.",
},
{
icon: Workflow,
title: "Интеграция с CRM",
description: "Бот пишет и читает данные напрямую из вашей CRM-системы.",
},
{
icon: Mail,
title: "Рассылки",
description: "Массовые и сегментированные рассылки по базе клиентов.",
},
];
export default function BotsPage() {
return (
<>
<PageHero
eyebrow="Боты для соцсетей"
title="Боты, которые"
highlight="работают без выходных"
subtitle="Создаём Telegram- и VK-ботов любой сложности: автоматизация заявок, уведомления, интеграция с CRM и сервисами вашего бизнеса."
>
<Link
href="/contacts"
className="inline-flex items-center gap-1.5 rounded-full bg-blue-500 px-6 py-3.5 font-medium text-white transition-transform hover:scale-[1.03]"
>
Обсудить бота
<ArrowUpRight size={18} />
</Link>
</PageHero>
<section className="px-6 py-20">
<div className="mx-auto grid max-w-6xl gap-5 lg:grid-cols-2">
{platforms.map((platform) => (
<div
key={platform.title}
className="rounded-2xl border border-white/[0.06] bg-base-900/50 p-8"
>
<div className="mb-5 flex h-11 w-11 items-center justify-center rounded-xl border border-blue-500/25 bg-blue-500/10 text-blue-400">
<platform.icon size={20} strokeWidth={1.75} />
</div>
<h3 className="font-display text-xl font-medium">{platform.title}</h3>
<p className="mt-3 text-ink-500">{platform.description}</p>
<ul className="mt-6 space-y-3">
{platform.points.map((point) => (
<li key={point} className="flex items-start gap-2.5 text-sm text-ink-300">
<Check size={17} className="mt-0.5 shrink-0 text-blue-400" />
{point}
</li>
))}
</ul>
</div>
))}
</div>
</section>
<section className="px-6 py-20">
<div className="mx-auto max-w-6xl">
<SectionEyebrow>Сценарии использования</SectionEyebrow>
<h2 className="font-display text-3xl font-medium sm:text-4xl">
Что умеют наши боты
</h2>
<div className="mt-12 grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{useCases.map((item) => (
<div
key={item.title}
className="rounded-2xl border border-white/[0.06] bg-base-900/40 p-6"
>
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg border border-white/[0.08] bg-white/[0.03] text-blue-400">
<item.icon size={18} strokeWidth={1.75} />
</div>
<h3 className="font-medium text-ink-100">{item.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-ink-500">
{item.description}
</p>
</div>
))}
</div>
</div>
</section>
<CTASection
title="Готовы автоматизировать общение с клиентами?"
subtitle="Опишите задачу — предложим сценарий бота и сроки разработки."
/>
</>
);
}
+107
View File
@@ -0,0 +1,107 @@
import type { Metadata } from "next";
import { Send, MessageCircle, Phone, Mail, MapPin, Clock } from "lucide-react";
import SectionEyebrow from "@/components/SectionEyebrow";
import NetworkGraphic from "@/components/NetworkGraphic";
import ContactForm from "@/components/ContactForm";
import { contacts } from "@/config/contacts";
export const metadata: Metadata = {
title: "Контакты",
description: "Свяжитесь с командой LabNetHub через форму, Telegram, WhatsApp или email.",
};
const directContacts = [
{
icon: Send,
label: "Telegram",
value: "Написать в чат",
href: contacts.telegramBot,
},
{
icon: Phone,
label: "WhatsApp",
value: "Написать в WhatsApp",
href: contacts.whatsapp,
},
{
icon: Mail,
label: "Email",
value: contacts.email,
href: `mailto:${contacts.email}`,
},
{
icon: MessageCircle,
label: "ВКонтакте",
value: "Наше сообщество",
href: contacts.vk,
},
];
export default function ContactsPage() {
return (
<>
<section className="relative overflow-hidden border-b border-white/[0.06] px-6 pb-16 pt-32 sm:pb-20 sm:pt-40">
<div className="pointer-events-none absolute right-0 top-0 w-[420px] max-w-[60%] opacity-70 sm:opacity-100">
<NetworkGraphic className="w-full" />
</div>
<div className="mx-auto max-w-3xl">
<SectionEyebrow>Контакты</SectionEyebrow>
<h1 className="text-balance font-display text-4xl font-medium leading-[1.1] sm:text-5xl">
Свяжитесь <span className="text-gradient-blue">с нами</span>
</h1>
<p className="mt-5 max-w-xl text-lg leading-relaxed text-ink-500">
Расскажите о задаче предложим решение и сроки. Форма ниже
приходит нам напрямую в Telegram.
</p>
</div>
</section>
<section className="px-6 py-20">
<div className="mx-auto grid max-w-6xl gap-12 lg:grid-cols-[1.1fr_0.9fr]">
<div>
<h2 className="mb-6 font-display text-xl font-medium">
Напишите нам
</h2>
<ContactForm />
</div>
<div>
<h2 className="mb-6 font-display text-xl font-medium">
Прямые контакты
</h2>
<div className="space-y-3">
{directContacts.map((c) => (
<a
key={c.label}
href={c.href}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-4 rounded-xl border border-white/[0.06] bg-base-900/50 p-4 transition-colors hover:border-blue-500/30"
>
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg border border-blue-500/25 bg-blue-500/10 text-blue-400">
<c.icon size={18} strokeWidth={1.75} />
</div>
<div>
<p className="text-sm text-ink-500">{c.label}</p>
<p className="font-medium text-ink-100">{c.value}</p>
</div>
</a>
))}
</div>
<div className="mt-6 space-y-3 border-t border-white/[0.06] pt-6">
<div className="flex items-center gap-3 text-sm text-ink-500">
<MapPin size={16} className="text-ink-700" />
{contacts.city}
</div>
<div className="flex items-center gap-3 text-sm text-ink-500">
<Clock size={16} className="text-ink-700" />
{contacts.workingHours}
</div>
</div>
</div>
</div>
</section>
</>
);
}
+143
View File
@@ -0,0 +1,143 @@
import type { Metadata } from "next";
import Link from "next/link";
import { ArrowUpRight, Check, Lock, Wallet, Settings2, Database } from "lucide-react";
import PageHero from "@/components/PageHero";
import SectionEyebrow from "@/components/SectionEyebrow";
import CTASection from "@/components/CTASection";
export const metadata: Metadata = {
title: "Настройка и интеграция CRM",
description:
"Установка, настройка и интеграция CRM-систем с открытым исходным кодом под процессы вашего бизнеса.",
};
const benefits = [
{
icon: Wallet,
title: "Без лицензионных платежей",
description: "Открытый исходный код — платите за настройку, а не за подписку на каждого пользователя.",
},
{
icon: Lock,
title: "Данные остаются у вас",
description: "CRM разворачивается на вашем сервере — полный контроль над данными клиентов.",
},
{
icon: Settings2,
title: "Гибкая настройка",
description: "Дорабатываем систему под ваши процессы, а не подстраиваем бизнес под шаблон.",
},
{
icon: Database,
title: "Свобода интеграций",
description: "Подключаем сайт, боты, почту и другие сервисы без ограничений вендора.",
},
];
const systems = [
{
name: "EspoCRM",
description: "Гибкая CRM с удобным конструктором сущностей и процессов под любые отрасли.",
},
{
name: "SuiteCRM",
description: "Развитая система с модулями продаж, поддержки и маркетинга из коробки.",
},
{
name: "Odoo CRM",
description: "Часть экосистемы Odoo — легко расширяется складом, документами и бухгалтерией.",
},
];
const included = [
"Установка и настройка CRM на сервере",
"Настройка воронок продаж и полей под ваши процессы",
"Интеграция с сайтом, ботами и почтой",
"Перенос текущей базы клиентов",
"Обучение сотрудников работе с системой",
"Техническая поддержка после запуска",
];
export default function CrmPage() {
return (
<>
<PageHero
eyebrow="CRM-системы"
title="Настройка и"
highlight="интеграция CRM"
subtitle="Устанавливаем, настраиваем и интегрируем CRM с открытым исходным кодом — под процессы именно вашего бизнеса."
>
<Link
href="/contacts"
className="inline-flex items-center gap-1.5 rounded-full bg-blue-500 px-6 py-3.5 font-medium text-white transition-transform hover:scale-[1.03]"
>
Обсудить внедрение
<ArrowUpRight size={18} />
</Link>
</PageHero>
<section className="px-6 py-20">
<div className="mx-auto max-w-6xl">
<SectionEyebrow>Почему open source</SectionEyebrow>
<div className="mt-8 grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
{benefits.map((b) => (
<div key={b.title}>
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg border border-white/[0.08] bg-white/[0.03] text-blue-400">
<b.icon size={18} strokeWidth={1.75} />
</div>
<h3 className="font-medium text-ink-100">{b.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-ink-500">
{b.description}
</p>
</div>
))}
</div>
</div>
</section>
<section className="px-6 py-20">
<div className="mx-auto max-w-6xl">
<SectionEyebrow>С какими системами работаем</SectionEyebrow>
<h2 className="font-display text-3xl font-medium sm:text-4xl">
Примеры CRM с открытым кодом
</h2>
<div className="mt-12 grid gap-5 lg:grid-cols-3">
{systems.map((s) => (
<div
key={s.name}
className="rounded-2xl border border-white/[0.06] bg-base-900/50 p-7"
>
<h3 className="font-display text-lg font-medium">{s.name}</h3>
<p className="mt-3 text-sm leading-relaxed text-ink-500">
{s.description}
</p>
</div>
))}
</div>
<p className="mt-6 text-sm text-ink-700">
Также работаем с другими open-source CRM подберём систему под ваши задачи и бюджет.
</p>
</div>
</section>
<section className="px-6 py-20">
<div className="mx-auto max-w-6xl rounded-3xl border border-white/[0.06] bg-base-900/40 p-10">
<SectionEyebrow>Что входит в услугу</SectionEyebrow>
<div className="mt-6 grid gap-4 sm:grid-cols-2">
{included.map((item) => (
<div key={item} className="flex items-start gap-2.5">
<Check size={17} className="mt-0.5 shrink-0 text-blue-400" />
<span className="text-ink-300">{item}</span>
</div>
))}
</div>
</div>
</section>
<CTASection
title="Хотите навести порядок в клиентской базе?"
subtitle="Расскажем, какая CRM подойдёт именно вашим процессам."
/>
</>
);
}
+68
View File
@@ -0,0 +1,68 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html {
scroll-behavior: smooth;
}
body {
@apply bg-base-950 text-ink-100 font-body antialiased;
background-image: theme("backgroundImage.grid-fade");
background-attachment: fixed;
}
h1,
h2,
h3,
h4 {
@apply font-display text-ink-100;
}
::selection {
@apply bg-blue-600/40 text-ink-100;
}
:focus-visible {
outline: 2px solid theme("colors.blue.400");
outline-offset: 3px;
border-radius: 2px;
}
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
@apply bg-base-950;
}
::-webkit-scrollbar-thumb {
@apply bg-base-700 rounded-full;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-blue-700;
}
}
@layer utilities {
.text-gradient-blue {
@apply bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-blue-600;
}
.text-gradient-green {
@apply bg-clip-text text-transparent bg-gradient-to-r from-green-300 to-green-500;
}
.clip-corner {
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 22px), calc(100% - 22px) 100%, 0 100%);
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}
}
+134
View File
@@ -0,0 +1,134 @@
import type { Metadata } from "next";
import Link from "next/link";
import {
ArrowUpRight,
Check,
MapPin,
Globe,
Gauge,
Settings2,
Cpu,
} from "lucide-react";
import PageHero from "@/components/PageHero";
import SectionEyebrow from "@/components/SectionEyebrow";
import CTASection from "@/components/CTASection";
export const metadata: Metadata = {
title: "Хостинг и серверы",
description:
"VDS/VPS на собственных серверах в Астрахани и у партнёров по всему миру. NVMe, SLA 99.9%, защита от DDoS.",
};
const features = [
{
icon: MapPin,
title: "Собственный дата-центр",
description: "Современное оборудование в нашем дата-центре в Астрахани.",
},
{
icon: Globe,
title: "Локации по всему миру",
description: "Европа, Азия, США и другие регионы у партнёров.",
},
{
icon: Gauge,
title: "Высокая производительность",
description: "NVMe-диски и мощные процессоры для стабильной нагрузки.",
},
{
icon: Settings2,
title: "Гибкие конфигурации",
description: "Соберём сервер под ваши задачи, а не по шаблону.",
},
];
const specs = [
"От 1 до 32 vCPU",
"От 1 до 128 GB RAM",
"NVMe SSD",
"Linux / Windows",
"SLA 99.9%",
"Защита от DDoS",
"Резервное копирование",
"Панель управления и API",
];
export default function HostingPage() {
return (
<>
<PageHero
accent="green"
eyebrow="Хостинг и серверы"
title="Надёжные серверы"
highlight="по всему миру"
subtitle="Предоставляем мощные и стабильные VDS/VPS — на собственных серверах в Астрахани и в других локациях у проверенных партнёров."
>
<Link
href="/contacts"
className="inline-flex items-center gap-1.5 rounded-full bg-green-500 px-6 py-3.5 font-medium text-base-950 transition-transform hover:scale-[1.03]"
>
Подобрать сервер
<ArrowUpRight size={18} />
</Link>
</PageHero>
<section className="px-6 py-20">
<div className="mx-auto max-w-6xl grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
{features.map((f) => (
<div key={f.title}>
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg border border-green-500/25 bg-green-500/10 text-green-400">
<f.icon size={18} strokeWidth={1.75} />
</div>
<h3 className="font-medium text-ink-100">{f.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-ink-500">
{f.description}
</p>
</div>
))}
</div>
</section>
<section className="px-6 py-20">
<div className="mx-auto max-w-4xl rounded-3xl border border-green-500/20 bg-green-500/[0.04] p-10">
<div className="flex items-center gap-3">
<div className="flex h-11 w-11 items-center justify-center rounded-xl border border-green-500/25 bg-green-500/10 text-green-400">
<Cpu size={20} strokeWidth={1.75} />
</div>
<h2 className="font-display text-xl font-medium">
VDS/VPS от LabNetHub
</h2>
</div>
<div className="mt-8 grid gap-4 sm:grid-cols-2">
{specs.map((spec) => (
<div key={spec} className="flex items-start gap-2.5">
<Check size={17} className="mt-0.5 shrink-0 text-green-400" />
<span className="text-ink-300">{spec}</span>
</div>
))}
</div>
<p className="mt-8 text-sm text-ink-500">
Стоимость зависит от конфигурации и локации рассчитаем тариф под
вашу задачу.
</p>
</div>
</section>
<section className="px-6 pb-4">
<div className="mx-auto max-w-6xl">
<SectionEyebrow accent="green">Размещение проектов</SectionEyebrow>
<p className="max-w-2xl text-ink-500">
Размещаем сайты и боты, разработанные нашей командой, на
собственных серверах или у партнёров без необходимости искать
хостинг отдельно и разбираться в настройке сервера самостоятельно.
</p>
</div>
</section>
<CTASection
title="Нужен сервер под проект?"
subtitle="Подберём конфигурацию и локацию, поможем с переносом и настройкой."
accent="green"
/>
</>
);
}
+11
View File
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#6FA8FF" />
<stop offset="100%" stop-color="#2F63EB" />
</linearGradient>
</defs>
<rect width="40" height="40" rx="9" fill="#0A1428" />
<polygon points="10,7 18,7 15,32 7,32" fill="#F5F7FB" />
<polygon points="14,32 33,32 28,24 18,24" fill="url(#g)" />
</svg>

After

Width:  |  Height:  |  Size: 440 B

+59
View File
@@ -0,0 +1,59 @@
import type { Metadata } from "next";
import { Unbounded, Manrope, JetBrains_Mono } from "next/font/google";
import "./globals.css";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
const unbounded = Unbounded({
subsets: ["latin", "cyrillic"],
weight: ["400", "500", "600"],
variable: "--font-unbounded",
display: "swap",
});
const manrope = Manrope({
subsets: ["latin", "cyrillic"],
weight: ["400", "500", "600", "700"],
variable: "--font-manrope",
display: "swap",
});
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin", "cyrillic"],
weight: ["400", "500"],
variable: "--font-jetbrains",
display: "swap",
});
export const metadata: Metadata = {
metadataBase: new URL("https://labnethub.ru"),
title: {
default: "LabNetHub — комплексные IT-решения для бизнеса",
template: "%s — LabNetHub",
},
description:
"Сайты, боты для соцсетей, настройка CRM, хостинг и IT-поддержка. От идеи до стабильной работы вашей инфраструктуры.",
openGraph: {
title: "LabNetHub — комплексные IT-решения для бизнеса",
description:
"Сайты, боты для соцсетей, настройка CRM, хостинг и IT-поддержка.",
locale: "ru_RU",
type: "website",
},
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ru" className={`${unbounded.variable} ${manrope.variable} ${jetbrainsMono.variable}`}>
<body>
<Header />
<main className="min-h-screen pt-[73px]">{children}</main>
<Footer />
</body>
</html>
);
}
+113
View File
@@ -0,0 +1,113 @@
import Link from "next/link";
import { ArrowUpRight, ShieldCheck, Rocket, SlidersHorizontal, Headphones } from "lucide-react";
import SectionEyebrow from "@/components/SectionEyebrow";
import ServiceCard from "@/components/ServiceCard";
import FeatureBadge from "@/components/FeatureBadge";
import NetworkGraphic from "@/components/NetworkGraphic";
import CTASection from "@/components/CTASection";
import { services } from "@/config/services";
export default function HomePage() {
return (
<>
<section className="relative overflow-hidden px-6 pb-20 pt-32 sm:pb-28 sm:pt-40">
<div className="pointer-events-none absolute right-[-40px] top-10 w-[480px] max-w-[65%] opacity-60 sm:opacity-100">
<NetworkGraphic accent="blue" className="w-full" />
</div>
<div className="mx-auto max-w-3xl">
<SectionEyebrow>Технологии · Решения · Поддержка</SectionEyebrow>
<h1 className="text-balance font-display text-4xl font-medium leading-[1.08] sm:text-6xl">
Комплексные IT-решения{" "}
<span className="text-gradient-blue">для вашего бизнеса</span>
</h1>
<p className="mt-6 max-w-xl text-lg leading-relaxed text-ink-500">
От идеи до стабильной работы инфраструктуры: сайты, боты для
соцсетей, CRM, серверы и техническая поддержка в одной команде.
</p>
<div className="mt-9 flex flex-wrap gap-3">
<Link
href="/contacts"
className="inline-flex items-center gap-1.5 rounded-full bg-blue-500 px-6 py-3.5 font-medium text-white transition-transform hover:scale-[1.03]"
>
Обсудить проект
<ArrowUpRight size={18} />
</Link>
<Link
href="#services"
className="inline-flex items-center gap-1.5 rounded-full border border-white/[0.12] px-6 py-3.5 font-medium text-ink-100 transition-colors hover:bg-white/[0.05]"
>
Смотреть услуги
</Link>
</div>
</div>
<div className="mx-auto mt-16 grid max-w-3xl grid-cols-2 gap-6 border-t border-white/[0.06] pt-8 sm:grid-cols-4">
{[
["5", "направлений услуг"],
["24/7", "техподдержка"],
["99.9%", "SLA на серверах"],
["1", "команда на весь проект"],
].map(([value, label]) => (
<div key={label}>
<p className="font-display text-2xl text-ink-100">{value}</p>
<p className="mt-1 text-sm text-ink-500">{label}</p>
</div>
))}
</div>
</section>
<section id="services" className="scroll-mt-24 px-6 py-20">
<div className="mx-auto max-w-6xl">
<div className="max-w-2xl">
<SectionEyebrow>Что мы делаем</SectionEyebrow>
<h2 className="font-display text-3xl font-medium sm:text-4xl">
Всё для роста вашего бизнеса в одном месте
</h2>
<p className="mt-4 text-ink-500">
Каждое направление можно заказать отдельно или как часть общего
решения от первого сайта до полноценной IT-инфраструктуры.
</p>
</div>
<div className="mt-12 grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{services.map((service) => (
<ServiceCard key={service.key} service={service} />
))}
</div>
</div>
</section>
<section className="px-6 py-20">
<div className="mx-auto max-w-6xl rounded-3xl border border-white/[0.06] bg-base-900/40 p-10">
<SectionEyebrow>Почему выбирают нас</SectionEyebrow>
<div className="mt-6 grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
<FeatureBadge
icon={ShieldCheck}
title="Надёжность"
description="Гарантия качества и стабильной работы решений"
/>
<FeatureBadge
icon={Rocket}
title="Скорость"
description="Быстрое выполнение задач и запуск проектов"
/>
<FeatureBadge
icon={SlidersHorizontal}
title="Индивидуальный подход"
description="Решения под конкретные задачи вашего бизнеса"
/>
<FeatureBadge
icon={Headphones}
title="Поддержка 24/7"
description="Мы всегда на связи и готовы помочь"
/>
</div>
</div>
</section>
<CTASection />
</>
);
}
+120
View File
@@ -0,0 +1,120 @@
import type { Metadata } from "next";
import Link from "next/link";
import { ArrowUpRight, Server, Activity, Wrench, MessageSquare, Check } from "lucide-react";
import PageHero from "@/components/PageHero";
import SectionEyebrow from "@/components/SectionEyebrow";
import CTASection from "@/components/CTASection";
export const metadata: Metadata = {
title: "IT-поддержка и инфраструктура",
description:
"Обслуживание IT-инфраструктуры бизнеса: сборка серверов, мониторинг, устранение сбоев, консультации.",
};
const coverage = [
{
icon: Server,
title: "Обслуживание серверов",
description: "Настройка, обновление и сопровождение серверного оборудования.",
},
{
icon: Activity,
title: "Мониторинг и профилактика",
description: "Отслеживаем состояние систем, чтобы предупредить сбои заранее.",
},
{
icon: Wrench,
title: "Сборка и настройка оборудования",
description: "Подбираем и настраиваем технику под задачи вашей команды.",
},
{
icon: MessageSquare,
title: "Консультации",
description: "Помогаем принимать решения по развитию IT-инфраструктуры.",
},
];
const formats = [
{
title: "Разовая задача",
description:
"Точечная помощь: настройка сервера, устранение конкретной проблемы, консультация.",
points: ["Без долгосрочных обязательств", "Оценка сроков после диагностики задачи"],
},
{
title: "Постоянное обслуживание",
description:
"Регулярное сопровождение инфраструктуры: мониторинг, обновления, приоритетная поддержка.",
points: ["Согласованный формат и время реакции", "Профилактика вместо экстренных починок"],
},
];
export default function SupportPage() {
return (
<>
<PageHero
eyebrow="IT-поддержка"
title="Поддержка вашей"
highlight="IT-инфраструктуры"
subtitle="Обслуживаем существующую инфраструктуру бизнеса: от сборки и настройки оборудования до мониторинга и устранения сбоев."
>
<Link
href="/contacts"
className="inline-flex items-center gap-1.5 rounded-full bg-blue-500 px-6 py-3.5 font-medium text-white transition-transform hover:scale-[1.03]"
>
Обсудить задачу
<ArrowUpRight size={18} />
</Link>
</PageHero>
<section className="px-6 py-20">
<div className="mx-auto max-w-6xl grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
{coverage.map((c) => (
<div key={c.title}>
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg border border-white/[0.08] bg-white/[0.03] text-blue-400">
<c.icon size={18} strokeWidth={1.75} />
</div>
<h3 className="font-medium text-ink-100">{c.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-ink-500">
{c.description}
</p>
</div>
))}
</div>
</section>
<section className="px-6 py-20">
<div className="mx-auto max-w-6xl">
<SectionEyebrow>Формат работы</SectionEyebrow>
<h2 className="font-display text-3xl font-medium sm:text-4xl">
Как удобно вам
</h2>
<div className="mt-12 grid gap-5 lg:grid-cols-2">
{formats.map((f) => (
<div
key={f.title}
className="rounded-2xl border border-white/[0.06] bg-base-900/50 p-8"
>
<h3 className="font-display text-lg font-medium">{f.title}</h3>
<p className="mt-3 text-ink-500">{f.description}</p>
<ul className="mt-6 space-y-3">
{f.points.map((point) => (
<li key={point} className="flex items-start gap-2.5 text-sm text-ink-300">
<Check size={17} className="mt-0.5 shrink-0 text-blue-400" />
{point}
</li>
))}
</ul>
</div>
))}
</div>
</div>
</section>
<CTASection
title="Нужна помощь с инфраструктурой?"
subtitle="Опишите ситуацию — предложим формат работы и следующий шаг."
/>
</>
);
}
+160
View File
@@ -0,0 +1,160 @@
import type { Metadata } from "next";
import Link from "next/link";
import { Check, ArrowUpRight } from "lucide-react";
import PageHero from "@/components/PageHero";
import SectionEyebrow from "@/components/SectionEyebrow";
import CTASection from "@/components/CTASection";
export const metadata: Metadata = {
title: "Сайты для бизнеса",
description:
"Разработка сайтов-визиток и полноценных корпоративных сайтов для малого и среднего бизнеса.",
};
const offers = [
{
title: "Сайт-визитка",
description:
"Компактный сайт на 1–5 страниц: рассказать о компании, показать услуги и получить первые заявки.",
points: [
"Адаптивная вёрстка под любые экраны",
"Форма заявки и контакты на видном месте",
"Быстрый запуск — от нескольких дней",
],
},
{
title: "Корпоративный сайт под ключ",
description:
"Полноценный сайт для компании: каталог, блог, личный кабинет, интеграции с CRM и сервисами.",
points: [
"Структура под задачи бизнеса, а не шаблон",
"SEO-оптимизация и подготовка к продвижению",
"Интеграция с CRM, ботами и аналитикой",
],
},
];
const steps = [
{
n: "01",
title: "Бриф",
description: "Обсуждаем задачи бизнеса, аудиторию и нужный результат.",
},
{
n: "02",
title: "Прототип и дизайн",
description: "Собираем структуру страниц и визуальный стиль сайта.",
},
{
n: "03",
title: "Разработка",
description: "Вёрстка и программирование с адаптацией под все устройства.",
},
{
n: "04",
title: "Тестирование",
description: "Проверяем работу форм, скорость и корректность на разных устройствах.",
},
{
n: "05",
title: "Запуск и поддержка",
description: "Публикуем сайт и остаёмся на связи для доработок.",
},
];
const included = [
"Адаптивная вёрстка под мобильные, планшеты и десктоп",
"SEO-оптимизация: метатеги, семантическая разметка, скорость загрузки",
"Интеграция форм обратной связи с Telegram, email или CRM",
"Подключение аналитики (Яндекс.Метрика, Google Analytics)",
"Настройка домена, SSL-сертификата и хостинга",
"Инструкция по управлению сайтом после запуска",
];
export default function WebsitesPage() {
return (
<>
<PageHero
eyebrow="Разработка сайтов"
title="Сайты, которые"
highlight="работают на бизнес"
subtitle="Разрабатываем современные и адаптивные сайты для вашего бизнеса — от простой визитки до полноценного корпоративного портала."
>
<Link
href="/contacts"
className="inline-flex items-center gap-1.5 rounded-full bg-blue-500 px-6 py-3.5 font-medium text-white transition-transform hover:scale-[1.03]"
>
Обсудить проект
<ArrowUpRight size={18} />
</Link>
<Link
href="#process"
className="inline-flex items-center gap-1.5 rounded-full border border-white/[0.12] px-6 py-3.5 font-medium text-ink-100 transition-colors hover:bg-white/[0.05]"
>
Как мы работаем
</Link>
</PageHero>
<section className="px-6 py-20">
<div className="mx-auto grid max-w-6xl gap-5 lg:grid-cols-2">
{offers.map((offer) => (
<div
key={offer.title}
className="rounded-2xl border border-white/[0.06] bg-base-900/50 p-8"
>
<h3 className="font-display text-xl font-medium">{offer.title}</h3>
<p className="mt-3 text-ink-500">{offer.description}</p>
<ul className="mt-6 space-y-3">
{offer.points.map((point) => (
<li key={point} className="flex items-start gap-2.5 text-sm text-ink-300">
<Check size={17} className="mt-0.5 shrink-0 text-blue-400" />
{point}
</li>
))}
</ul>
</div>
))}
</div>
</section>
<section id="process" className="scroll-mt-24 px-6 py-20">
<div className="mx-auto max-w-6xl">
<SectionEyebrow>Как мы работаем</SectionEyebrow>
<h2 className="font-display text-3xl font-medium sm:text-4xl">
Пять шагов от идеи до запуска
</h2>
<div className="mt-12 grid gap-8 sm:grid-cols-2 lg:grid-cols-5">
{steps.map((step) => (
<div key={step.n}>
<p className="font-mono text-sm text-blue-400">{step.n}</p>
<h3 className="mt-2 font-display text-lg font-medium">
{step.title}
</h3>
<p className="mt-2 text-sm leading-relaxed text-ink-500">
{step.description}
</p>
</div>
))}
</div>
</div>
</section>
<section className="px-6 py-20">
<div className="mx-auto max-w-6xl rounded-3xl border border-white/[0.06] bg-base-900/40 p-10">
<SectionEyebrow>Что входит в разработку</SectionEyebrow>
<div className="mt-6 grid gap-4 sm:grid-cols-2">
{included.map((item) => (
<div key={item} className="flex items-start gap-2.5">
<Check size={17} className="mt-0.5 shrink-0 text-blue-400" />
<span className="text-ink-300">{item}</span>
</div>
))}
</div>
</div>
</section>
<CTASection />
</>
);
}