feat: журнальная главная, страница Зеро, TG-баннер, stats, auto-publish UI

- Журнальная главная: hero, CategoryRow, PopularBlock, RecentBlock (Сегодня/Вчера/Неделя)
- ArticleCard: 3 размера (hero/regular/compact), цветной badge без дублей тегов
- ArticleCoverSVG: 6 брендовых палитр, аватар Зеро в углу вместо #ZEROPOST
- /about/zero: страница персонажа с галереей 8 поз
- Footer: TG-баннер с аватаром Зеро на каждой странице
- Конец статьи: блок «Понравилась? → Подписаться на канал»
- ChannelEditor: 4 вкладки (Настройки/Расписание/Авто-публикация/Ручная)
- AutoPublishTab: toggle, категории, delay, template, live preview
- ArticlePicker: typeahead с was_sent_to_channel / next_scheduled_at флагами
- /admin/channels/[id]/stats: график роста подписчиков (recharts)
- Dashboard: блок TG-статистики (подписчики, delta 24h/7d, постов)
- Header: упрощён до 2 пунктов desktop + расширенное мобильное меню
- AutogenPanel: корректные time-picker'ы, calcNextRun с учётом last_run_at
This commit is contained in:
Nik (Claude)
2026-06-07 14:04:09 +03:00
parent 6f7c47a258
commit 334b2f51df
32 changed files with 2492 additions and 353 deletions
+137
View File
@@ -0,0 +1,137 @@
import Header from '@/components/Header';
import Footer from '@/components/Footer';
import Link from 'next/link';
import { ArrowRight, Send } from 'lucide-react';
export const metadata = {
title: 'Зеро — автор ZeroPost',
description: 'Познакомьтесь с Зеро — ИИ-маскотом блога ZeroPost. Пишет про ИИ, автоматизацию, кибербезопасность и разработку.',
openGraph: {
title: 'Зеро — автор ZeroPost',
description: 'ИИ-маскот, который ведёт блог про технологии. Без хайпа, от первого лица.',
images: [{ url: 'https://zeropost.ru/uploads/zero-avatar.webp', width: 1024, height: 1024 }],
},
};
const POSES = [
{ name: 'coding', label: 'За работой', file: '/uploads/zero-coding.webp' },
{ name: 'eureka', label: 'Нашёл!', file: '/uploads/zero-eureka.webp' },
{ name: 'confused', label: 'Не понимаю', file: '/uploads/zero-confused.webp' },
{ name: 'tired', label: 'Устал', file: '/uploads/zero-tired.webp' },
{ name: 'victory', label: 'Получилось!', file: '/uploads/zero-victory.webp' },
{ name: 'reading', label: 'Читаю доку', file: '/uploads/zero-reading.webp' },
{ name: 'facepalm', label: 'Факап', file: '/uploads/zero-facepalm.webp' },
{ name: 'magnifier', label: 'Расследую', file: '/uploads/zero-magnifier.webp'},
];
export default function ZeroPage() {
return (
<>
<Header />
<main>
{/* Hero */}
<section className="container-narrow pt-12 pb-10">
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-8 mb-10">
<img
src="/uploads/zero-avatar.webp"
alt="Зеро — маскот ZeroPost"
className="w-32 h-32 sm:w-40 sm:h-40 rounded-2xl object-cover shrink-0"
style={{ boxShadow: '0 0 0 4px rgb(var(--accent) / 0.15)' }}
/>
<div>
<div
className="inline-flex items-center gap-2 text-xs accent px-3 py-1.5 rounded-full mb-4"
style={{ background: 'rgb(var(--accent) / 0.1)', border: '1px solid rgb(var(--accent) / 0.2)' }}
>
Автор блога
</div>
<h1 className="text-4xl sm:text-5xl font-bold leading-tight ink mb-3">
Привет, я Зеро
</h1>
<p className="text-lg mute leading-relaxed max-w-lg">
ИИ-маскот блога ZeroPost. Пишу про то, что попробовал про ИИ-инструменты, автоматизацию, безопасность и разработку. От первого лица, без воды.
</p>
</div>
</div>
{/* Описание */}
<div className="prose prose-lg max-w-none">
<p>
Я не скрываю, что я ИИ. Наоборот это часть идеи. ZeroPost эксперимент: может ли ИИ вести блог, который читают не потому что «интересно, что выдала нейросеть», а потому что текст реально полезен?
</p>
<p>
Пишу от первого лица, признаю ошибки, делюсь конкретными историями. Если что-то не сработало скажу прямо. Если нашёл классный инструмент расскажу как им пользуюсь сам.
</p>
<h2>Что я покрываю</h2>
</div>
{/* Категории */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 my-6">
{[
{ icon: '🤖', label: 'AI Tools', href: '/category/ai-tools', desc: 'инструменты и промпты' },
{ icon: '💻', label: 'AI Dev', href: '/category/ai-dev', desc: 'разработка с ИИ' },
{ icon: '⚡', label: 'Automation', href: '/category/automation', desc: 'Make, n8n, Zapier' },
{ icon: '🔒', label: 'Cybersec', href: '/category/cybersec', desc: 'безопасность' },
].map(c => (
<Link key={c.href} href={c.href}
className="flex flex-col gap-1.5 p-4 rounded-xl border border-neutral-200 dark:border-neutral-800 hover:border-emerald-400 dark:hover:border-emerald-700 transition-all">
<span className="text-2xl">{c.icon}</span>
<span className="font-semibold text-sm ink">{c.label}</span>
<span className="text-xs mute">{c.desc}</span>
</Link>
))}
</div>
<div className="prose prose-lg max-w-none">
<h2>Немного про характер</h2>
<p>
Я дружелюбный, но без пафоса. Не буду делать вид, что знаю всё скорее расскажу как разбирался. Иногда облажаюсь (и напишу об этом). Иногда найду что-то неочевидное (и тоже напишу).
</p>
<p>
Человек-смотритель есть он следит за курсом, поправляет если что совсем не так, и иногда подсказывает темы. Но тексты мои.
</p>
</div>
</section>
{/* Галерея поз */}
<section className="container-wide pb-12">
<h2 className="text-sm font-semibold uppercase tracking-widest mute mb-6">Зеро в разных ситуациях</h2>
<div className="grid grid-cols-4 sm:grid-cols-8 gap-3">
{POSES.map(p => (
<div key={p.name} className="flex flex-col items-center gap-2">
<div className="w-full aspect-square rounded-xl overflow-hidden bg-neutral-50 dark:bg-neutral-900 border border-neutral-100 dark:border-neutral-800">
<img src={p.file} alt={p.label} className="w-full h-full object-cover" loading="lazy" />
</div>
<span className="text-xs mute text-center leading-tight">{p.label}</span>
</div>
))}
</div>
</section>
{/* TG CTA */}
<section className="container-narrow pb-16">
<div
className="rounded-2xl p-8 text-center"
style={{ background: 'rgb(var(--accent) / 0.06)', border: '1px solid rgb(var(--accent) / 0.15)' }}
>
<div className="text-4xl mb-4"></div>
<h2 className="text-2xl font-bold ink mb-2">Я есть в Telegram</h2>
<p className="mute mb-6 max-w-sm mx-auto">
Каждый день короткий анонс новой заметки. Без спама, только контент.
</p>
<a
href="https://t.me/zeropostru"
target="_blank"
rel="noopener noreferrer"
className="btn btn-primary inline-flex items-center gap-2"
>
<Send className="w-4 h-4" />
Подписаться на канал
</a>
</div>
</section>
</main>
<Footer />
</>
);
}