// Каталог — публичная страница сайта S"MM. Шапка + sidebar + рабочая область.
const CAT = {
bg: '#f5ead4',
card: '#fbf2de',
cardHover: '#f3e7cb',
sub: '#e8ddc4',
panel: '#ecdfc1',
hover: '#e3d4b3',
active: '#d8c69e',
ink: '#1a1a1e',
dim: '#6a5a4a',
faint: '#9c8e7c',
fainter: '#b8ab97',
accent: '#c96848',
line: 'rgba(26,26,30,0.14)',
lineStrong: 'rgba(26,26,30,0.22)',
};
const CATSANS = '"Inter Tight", "Helvetica Neue", sans-serif';
const CATMONO = '"JetBrains Mono", ui-monospace, monospace';
const catMeta = { fontFamily: CATMONO, fontSize: 10, letterSpacing: '0.22em', textTransform: 'uppercase', color: CAT.dim };
// ── Иконки (контурные, в стиле основного сайта) ─────────────────────
const cIco = (paths, vb = '0 0 24 24') => ({ size = 20, stroke = 1.6 } = {}) => (
);
const CIconGrid = cIco(<>
>);
const CIconSpark = cIco(<>
>);
const CIconCheck = cIco(<>>);
const CIconArrR = cIco(<>
>);
// ── Шапка сайта (тонкая, та же, что на главной) ─────────────────────
// Навигация ведёт на главную страницу с указанием якоря. В Claude Design
// переходы между файлами могут не работать — это нормально.
const HOME = '/';
const SITE_NAV = [
{ l: 'услуги', href: `${HOME}#pricing` },
{ l: 'о нас', href: `${HOME}#philosophy` },
{ l: 'faq', href: `${HOME}#faq` },
{ l: 'контакты', href: `${HOME}#contacts` },
{ l: 'войти', href: '/login' },
];
function CatTopBar() {
const palette = { '--bg': CAT.bg, '--ink': CAT.ink, '--accent': CAT.accent, '--sub': CAT.sub };
const isMobile = window.useIsMobile ? window.useIsMobile() : false;
const [menuOpen, setMenuOpen] = React.useState(false);
return (
<>
{isMobile && menuOpen && (
)}
>
);
}
// ── Sidebar каталога ────────────────────────────────────────────────
function CatNavItem({ Icon, label, active, onClick }) {
const [hov, setHov] = React.useState(false);
const bg = active ? CAT.active : (hov ? CAT.hover : 'transparent');
const color = active ? CAT.accent : CAT.ink;
return (
{ e.preventDefault(); onClick(); }}
onMouseEnter={() => setHov(true)} onMouseLeave={() => setHov(false)}
style={{
position: 'relative', display: 'flex', alignItems: 'center', gap: 12,
padding: '12px 18px 12px 21px', textDecoration: 'none', color,
background: bg, transition: 'background .15s, color .15s',
fontFamily: CATSANS, fontSize: 14.5, fontWeight: active ? 500 : 400,
}}>
{active && (
)}
{label}
);
}
function CatSidebar({ active, onSelect }) {
return (
);
}
// ── Page heading ────────────────────────────────────────────────────
function CatPageHeading({ title, sub }) {
return (
);
}
// ── Хост страницы ───────────────────────────────────────────────────
function CatTabsRow({ active, onSelect }) {
const tabs = [
{ id: 'tariffs', Icon: CIconGrid, label: 'Тарифы' },
{ id: 'services', Icon: CIconSpark, label: 'Услуги' },
];
return (
{tabs.map((t) => {
const on = active === t.id;
return (
{ e.preventDefault(); onSelect(t.id); }}
style={{
flex: 1, display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
gap: 8, padding: '14px 12px', textDecoration: 'none',
color: on ? CAT.accent : CAT.ink,
fontFamily: CATSANS, fontSize: 15, fontWeight: on ? 600 : 400,
borderBottom: on ? `2px solid ${CAT.accent}` : '2px solid transparent',
}}>
{t.label}
);
})}
);
}
function CatalogPage() {
const isMobile = window.useIsMobile ? window.useIsMobile() : false;
const [section, setSection] = React.useState('tariffs');
const goTo = (id) => {
setSection(id);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
return (
{isMobile &&
}
{!isMobile && }
{section === 'tariffs' && window.CatalogTariffs
? goTo('services')} />
: section === 'services' && window.CatalogServices
?
: null}
);
}
// Раздел «Тарифы» ─────────────────────────────────────────────────────
const PLAN_TIERS_FULL = [
{
name: 'Тир-1',
sub: 'для одной соцсети, до 10 постов',
price: '8 900',
popular: false,
features: [
'1 соцсеть на выбор (VK или Telegram)',
'10 постов в месяц',
'Контент-план на месяц',
'Анализ конкурентов',
'1 идея для контента',
'Отчёт в конце месяца',
],
},
{
name: 'Тир-2',
sub: 'для двух соцсетей, до 15 постов',
price: '13 900',
popular: true,
features: [
'2 соцсети (VK + Telegram, кросспостинг)',
'15 постов в месяц',
'2 истории',
'2 внеплановых поста на инфоповоды',
'Контент-план на месяц',
'Анализ конкурентов',
'2 идеи для контента',
'Отчёт в конце месяца',
],
},
{
name: 'Тир-3',
sub: 'для двух соцсетей, до 20 постов',
price: '18 500',
popular: false,
features: [
'2 соцсети (VK + Telegram, кросспостинг)',
'20 постов в месяц',
'4 истории',
'2 графических рилса',
'4 внеплановых поста на инфоповоды',
'Контент-план на месяц',
'Анализ конкурентов',
'5 идей для контента',
'Отчёт в конце месяца',
],
},
];
const COMPARE_ROWS = [
{ p: 'Цена в месяц', v: ['8 900 ₽', '13 900 ₽', '18 500 ₽'] },
{ p: 'Соцсети', v: ['1', '2', '2'] },
{ p: 'Постов в месяц', v: ['10', '15', '20'] },
{ p: 'Историй в месяц', v: ['—', '2', '4'] },
{ p: 'Графических рилсов', v: ['—', '—', '2'] },
{ p: 'Внеплановых постов', v: ['—', '2', '4'] },
{ p: 'Идей для контента', v: ['1', '2', '5'] },
{ p: 'Контент-план', v: ['check', 'check', 'check'] },
{ p: 'Анализ конкурентов', v: ['check', 'check', 'check'] },
{ p: 'Отчётов в месяц', v: ['1', '1', '1'] },
];
// ── Тарифы для SMM-специалистов (диапазонная модель, оплата за слоты) ──
// ВНИМАНИЕ: предварительные цифры, синхронить с DIY_RANGES в боте
// (orchestrator tariffs.py). Реальная цена при покупке считается ботом —
// витрина только показывает модель.
const DIY_MIN_SLOTS = 2;
const DIY_RANGES = [
{ min: 2, max: 3, per: 4000 },
{ min: 4, max: 6, per: 3700 },
{ min: 7, max: 11, per: 3590 },
{ min: 12, max: null, per: null }, // договорная
];
// Что входит на каждого клиента (пакет diy_client).
const DIY_CLIENT_FEATURES = [
'15 постов в месяц',
'2 истории',
'1 контент-план',
'1 аудит конкурентов',
'1 отчёт клиенту',
'2 соцсети (VK + Telegram, кросспостинг)',
'Генерация изображений',
];
const DIY_FEATURES_NOTE = 'Рилсы — докупкой';
function diyRangeFor(n) {
return DIY_RANGES.find((r) => n >= r.min && (r.max == null || n <= r.max)) || null;
}
function CompareCell({ value }) {
if (value === 'check') {
return (
);
}
if (value === 'check-each') {
return (
(для каждой)
);
}
return {value};
}
function TierCard({ t }) {
return (
);
}
function CompareTable() {
return (
{/* Заголовок */}
параметр
{['Тир-1', 'Тир-2', 'Тир-3'].map((label, i) => (
{label}
))}
{/* Строки */}
{COMPARE_ROWS.map((row, i) => (
{row.p}
{row.v.map((val, j) => (
))}
))}
);
}
// ── Витрина для SMM-специалистов: карточка-калькулятор слотов ────────
function DiyStepBtn({ label, onClick, disabled }) {
return (
);
}
function DiyShowcase() {
const isMobile = window.useIsMobile ? window.useIsMobile() : false;
const [count, setCount] = React.useState(3);
const range = diyRangeFor(count);
const negotiable = !range || range.per == null; // 12+ → договорная
const fmt = (n) => n.toLocaleString('ru-RU');
const total = (!negotiable && range) ? range.per * count : null;
const rangeLabel = (r) => r.max == null ? `${r.min}+` : `${r.min}–${r.max}`;
return (
Для SMM-специалистов
Ведёте клиентов сами? Платите за слоты — ставка снижается с количеством.
{/* Левая колонка: калькулятор */}
сколько клиентов ведёте?
setCount((v) => Math.max(DIY_MIN_SLOTS, v - 1))} />
{count}
setCount((v) => v + 1)} />
{/* Расчёт */}
{negotiable ? (
<>
Договорная
от 12 клиентов — обсудим условия
>
) : (
<>
{fmt(range.per)} ₽ × {count} {count === 1 ? 'клиент' : (count < 5 ? 'клиента' : 'клиентов')}
{fmt(total)}
₽ в месяц
>
)}
{/* Мини-лесенка диапазонов */}
{DIY_RANGES.map((r, i) => {
const active = range && r.min === range.min;
return (
{rangeLabel(r)} {r.max == null || r.max > 1 ? 'клиентов' : 'клиента'}
{r.per == null ? 'договорная' : `${fmt(r.per)} ₽/клиент`}
);
})}
{/* Правая колонка: что входит + CTA */}
);
}
function CatalogTariffs({ onGoServices }) {
const isMobile = window.useIsMobile ? window.useIsMobile() : false;
return (
<>
{/* Карточки тарифов — на мобиле друг под другом (фичи перечислены внутри) */}
{PLAN_TIERS_FULL.map((t) => )}
{/* Сравнительная таблица — только на десктопе. На мобиле карточки выше
уже несут полный список фич, широкая таблица не нужна. */}
{!isMobile && (
)}
{/* Витрина для SMM-специалистов (диапазонная модель слотов) */}
{/* CTA в услуги */}
Нужно что-то ещё?
Разовые услуги, аудиты, кампании — посмотрите каталог услуг
>
);
}
Object.assign(window, { CatalogPage, CatPageHeading, CatalogTariffs, CatSidebar, CAT, CATSANS, CATMONO, catMeta });