+
Эксперимент: блог, который ведёт ИИ
-
+
ZeroPost — это два связанных проекта: публичный блог, который ты сейчас читаешь, и сервис для ведения Telegram-каналов с помощью ИИ.
diff --git a/app/blog/[slug]/page.js b/app/blog/[slug]/page.js
index 52f093c..6e3ba85 100644
--- a/app/blog/[slug]/page.js
+++ b/app/blog/[slug]/page.js
@@ -30,7 +30,6 @@ export default async function ArticlePage({ params }) {
const article = await getArticle(slug);
if (!article) notFound();
- // Убираю H1 из контента — он уже идёт в заголовке страницы
const contentWithoutH1 = article.content.replace(/^#\s+.+$/m, '').trim();
const html = renderMarkdown(contentWithoutH1);
@@ -38,7 +37,7 @@ export default async function ArticlePage({ params }) {
<>
-
+
Все статьи
@@ -48,11 +47,11 @@ export default async function ArticlePage({ params }) {
))}
-
+
{article.title}
-
+
{article.author}
·
{formatDate(article.published_at)}
@@ -67,13 +66,13 @@ export default async function ArticlePage({ params }) {
-
- Хочешь такой же блог или канал в Telegram?
-
+
+ Хочешь такой же блог или канал в Telegram?
+
Открыть ZeroPost
diff --git a/app/globals.css b/app/globals.css
index 54821e5..7dd9c8a 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -2,18 +2,92 @@
@tailwind components;
@tailwind utilities;
+/* === Theme tokens === */
+:root {
+ --bg: 250 250 249; /* почти белый, тёплый */
+ --surface: 255 255 255; /* карточки */
+ --surface-2: 245 245 244; /* приглушённый фон */
+ --border: 231 229 228;
+ --ink: 28 25 23; /* основной текст */
+ --mute: 120 113 108; /* приглушённый текст */
+ --accent: 16 185 129; /* emerald-500 */
+ --accent-2: 5 150 105; /* emerald-600 */
+ --accent-soft: 209 250 229; /* emerald-100 */
+}
+
+.dark {
+ --bg: 10 10 10;
+ --surface: 20 20 20;
+ --surface-2: 28 28 28;
+ --border: 42 42 42;
+ --ink: 229 231 235;
+ --mute: 156 163 175;
+ --accent: 16 185 129;
+ --accent-2: 52 211 153;
+ --accent-soft: 6 78 59;
+}
+
@layer base {
- html { @apply bg-bg text-ink; }
+ html {
+ background: rgb(var(--bg));
+ color: rgb(var(--ink));
+ }
body { @apply font-sans antialiased; }
- ::selection { @apply bg-accent/30; }
+ ::selection { background: rgb(var(--accent) / 0.25); }
}
@layer components {
- .btn { @apply inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed; }
- .btn-primary { @apply btn bg-accent text-black hover:bg-accent2; }
- .btn-ghost { @apply btn text-mute hover:bg-surface hover:text-white; }
+ .btn {
+ @apply inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg font-medium transition-all disabled:opacity-50 disabled:cursor-not-allowed;
+ }
+ .btn-primary {
+ background: rgb(var(--accent));
+ color: white;
+ }
+ .btn-primary:hover { background: rgb(var(--accent-2)); }
+
+ .btn-ghost {
+ color: rgb(var(--mute));
+ }
+ .btn-ghost:hover {
+ background: rgb(var(--surface-2));
+ color: rgb(var(--ink));
+ }
+
.container-narrow { @apply max-w-3xl mx-auto px-4; }
.container-wide { @apply max-w-6xl mx-auto px-4; }
- .article-card { @apply bg-surface border border-border rounded-2xl p-6 hover:border-accent/40 transition-colors; }
- .tag { @apply inline-block text-xs px-2.5 py-1 rounded-full bg-surface2 text-mute hover:bg-surface hover:text-white transition-colors; }
+
+ .article-card {
+ background: rgb(var(--surface));
+ border: 1px solid rgb(var(--border));
+ @apply rounded-2xl p-6 transition-all duration-200;
+ }
+ .article-card:hover {
+ border-color: rgb(var(--accent) / 0.4);
+ transform: translateY(-2px);
+ box-shadow: 0 10px 25px -10px rgb(0 0 0 / 0.08);
+ }
+ .dark .article-card:hover {
+ box-shadow: 0 10px 25px -10px rgb(0 0 0 / 0.5);
+ }
+
+ .tag {
+ display: inline-block;
+ @apply text-xs px-2.5 py-1 rounded-full transition-colors;
+ background: rgb(var(--surface-2));
+ color: rgb(var(--mute));
+ }
+ .tag:hover {
+ background: rgb(var(--accent-soft));
+ color: rgb(var(--accent-2));
+ }
+
+ .surface { background: rgb(var(--surface)); }
+ .surface-2 { background: rgb(var(--surface-2)); }
+ .ink { color: rgb(var(--ink)); }
+ .mute { color: rgb(var(--mute)); }
+ .accent { color: rgb(var(--accent)); }
+ .border-soft { border-color: rgb(var(--border)); }
+ .border-b-soft { border-bottom: 1px solid rgb(var(--border)); }
+ .border-t-soft { border-top: 1px solid rgb(var(--border)); }
}
diff --git a/app/layout.js b/app/layout.js
index 319723a..7b5e54c 100644
--- a/app/layout.js
+++ b/app/layout.js
@@ -14,10 +14,22 @@ export const metadata = {
},
};
+// Защита от FOUC: ставим тему до первого рендера
+const themeInitScript = `
+(function() {
+ try {
+ var saved = localStorage.getItem('theme');
+ var theme = saved || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
+ if (theme === 'dark') document.documentElement.classList.add('dark');
+ } catch(e) {}
+})();
+`;
+
export default function RootLayout({ children }) {
return (
-
+
+
+
+
+ 404
+
+ Такой страницы нет — но это не повод грустить
+
+
+ На главную
+
+
+
+ >
+ );
+}
diff --git a/app/page.js b/app/page.js
index ee87643..49cade5 100644
--- a/app/page.js
+++ b/app/page.js
@@ -28,22 +28,25 @@ export default async function HomePage() {
{/* Hero */}
-
+
Блог, который ведёт ИИ
-
+
Практический ИИ.
- Без воды и хайпа.
+ Без воды и хайпа.
-
+
Промпты, кейсы, инструменты и разборы. Всё пишет ИИ — кроме редакторских заметок. Если хочешь так же вести свой Telegram-канал — попробуй наш сервис.
@@ -60,7 +63,7 @@ export default async function HomePage() {
{/* Rest */}
{rest.length > 0 && (
-
+
Последние материалы
@@ -71,18 +74,18 @@ export default async function HomePage() {
{articles.length === 0 && (
- Скоро здесь появятся первые статьи. ИИ уже работает над ними.
+ Скоро здесь появятся первые статьи. ИИ уже работает над ними.
)}
{/* Tags */}
{tags.length > 0 && (
- Темы
+ Темы
{tags.map(t => (
- #{t.tag} ({t.cnt})
+ #{t.tag} ({t.cnt})
))}
diff --git a/app/tag/[name]/page.js b/app/tag/[name]/page.js
index ea71561..6ce2879 100644
--- a/app/tag/[name]/page.js
+++ b/app/tag/[name]/page.js
@@ -21,13 +21,13 @@ export default async function TagPage({ params }) {
<>
-
+
Все статьи
- #{tag}
- {articles.length} {articles.length === 1 ? 'материал' : 'материалов'}
+ #{tag}
+ {articles.length} {articles.length === 1 ? 'материал' : 'материалов'}
{articles.length === 0 ? (
- Пока пусто.
+ Пока пусто.
) : (
{articles.map(a => )}
diff --git a/components/ArticleCard.js b/components/ArticleCard.js
index c2ffe39..b6241e5 100644
--- a/components/ArticleCard.js
+++ b/components/ArticleCard.js
@@ -7,22 +7,23 @@ export default function ArticleCard({ article, featured = false }) {
return (
{(article.tags || []).slice(0, 3).map(t => (
#{t}
))}
-
+
{article.title}
{article.excerpt && (
-
+
{article.excerpt}
)}
-
+
{formatDate(article.published_at)}
{article.reading_time && (
@@ -41,13 +42,13 @@ export default function ArticleCard({ article, featured = false }) {
#{t}
))}
-
+
{article.title}
{article.excerpt && (
-
{article.excerpt}
+ {article.excerpt}
)}
-
+
{formatDate(article.published_at)}
{article.reading_time && (
diff --git a/components/Footer.js b/components/Footer.js
index f556f1e..bfbe883 100644
--- a/components/Footer.js
+++ b/components/Footer.js
@@ -2,14 +2,14 @@ import Link from 'next/link';
export default function Footer() {
return (
-
ZeroPost — это два связанных проекта: публичный блог, который ты сейчас читаешь, и сервис для ведения Telegram-каналов с помощью ИИ.
diff --git a/app/blog/[slug]/page.js b/app/blog/[slug]/page.js index 52f093c..6e3ba85 100644 --- a/app/blog/[slug]/page.js +++ b/app/blog/[slug]/page.js @@ -30,7 +30,6 @@ export default async function ArticlePage({ params }) { const article = await getArticle(slug); if (!article) notFound(); - // Убираю H1 из контента — он уже идёт в заголовке страницы const contentWithoutH1 = article.content.replace(/^#\s+.+$/m, '').trim(); const html = renderMarkdown(contentWithoutH1); @@ -38,7 +37,7 @@ export default async function ArticlePage({ params }) { <>
+
{article.title}
-
+
{article.author}
·
{formatDate(article.published_at)}
@@ -67,13 +66,13 @@ export default async function ArticlePage({ params }) {
-
- Хочешь такой же блог или канал в Telegram?
-
+
+ Хочешь такой же блог или канал в Telegram?
+
Открыть ZeroPost
diff --git a/app/globals.css b/app/globals.css
index 54821e5..7dd9c8a 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -2,18 +2,92 @@
@tailwind components;
@tailwind utilities;
+/* === Theme tokens === */
+:root {
+ --bg: 250 250 249; /* почти белый, тёплый */
+ --surface: 255 255 255; /* карточки */
+ --surface-2: 245 245 244; /* приглушённый фон */
+ --border: 231 229 228;
+ --ink: 28 25 23; /* основной текст */
+ --mute: 120 113 108; /* приглушённый текст */
+ --accent: 16 185 129; /* emerald-500 */
+ --accent-2: 5 150 105; /* emerald-600 */
+ --accent-soft: 209 250 229; /* emerald-100 */
+}
+
+.dark {
+ --bg: 10 10 10;
+ --surface: 20 20 20;
+ --surface-2: 28 28 28;
+ --border: 42 42 42;
+ --ink: 229 231 235;
+ --mute: 156 163 175;
+ --accent: 16 185 129;
+ --accent-2: 52 211 153;
+ --accent-soft: 6 78 59;
+}
+
@layer base {
- html { @apply bg-bg text-ink; }
+ html {
+ background: rgb(var(--bg));
+ color: rgb(var(--ink));
+ }
body { @apply font-sans antialiased; }
- ::selection { @apply bg-accent/30; }
+ ::selection { background: rgb(var(--accent) / 0.25); }
}
@layer components {
- .btn { @apply inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed; }
- .btn-primary { @apply btn bg-accent text-black hover:bg-accent2; }
- .btn-ghost { @apply btn text-mute hover:bg-surface hover:text-white; }
+ .btn {
+ @apply inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg font-medium transition-all disabled:opacity-50 disabled:cursor-not-allowed;
+ }
+ .btn-primary {
+ background: rgb(var(--accent));
+ color: white;
+ }
+ .btn-primary:hover { background: rgb(var(--accent-2)); }
+
+ .btn-ghost {
+ color: rgb(var(--mute));
+ }
+ .btn-ghost:hover {
+ background: rgb(var(--surface-2));
+ color: rgb(var(--ink));
+ }
+
.container-narrow { @apply max-w-3xl mx-auto px-4; }
.container-wide { @apply max-w-6xl mx-auto px-4; }
- .article-card { @apply bg-surface border border-border rounded-2xl p-6 hover:border-accent/40 transition-colors; }
- .tag { @apply inline-block text-xs px-2.5 py-1 rounded-full bg-surface2 text-mute hover:bg-surface hover:text-white transition-colors; }
+
+ .article-card {
+ background: rgb(var(--surface));
+ border: 1px solid rgb(var(--border));
+ @apply rounded-2xl p-6 transition-all duration-200;
+ }
+ .article-card:hover {
+ border-color: rgb(var(--accent) / 0.4);
+ transform: translateY(-2px);
+ box-shadow: 0 10px 25px -10px rgb(0 0 0 / 0.08);
+ }
+ .dark .article-card:hover {
+ box-shadow: 0 10px 25px -10px rgb(0 0 0 / 0.5);
+ }
+
+ .tag {
+ display: inline-block;
+ @apply text-xs px-2.5 py-1 rounded-full transition-colors;
+ background: rgb(var(--surface-2));
+ color: rgb(var(--mute));
+ }
+ .tag:hover {
+ background: rgb(var(--accent-soft));
+ color: rgb(var(--accent-2));
+ }
+
+ .surface { background: rgb(var(--surface)); }
+ .surface-2 { background: rgb(var(--surface-2)); }
+ .ink { color: rgb(var(--ink)); }
+ .mute { color: rgb(var(--mute)); }
+ .accent { color: rgb(var(--accent)); }
+ .border-soft { border-color: rgb(var(--border)); }
+ .border-b-soft { border-bottom: 1px solid rgb(var(--border)); }
+ .border-t-soft { border-top: 1px solid rgb(var(--border)); }
}
diff --git a/app/layout.js b/app/layout.js
index 319723a..7b5e54c 100644
--- a/app/layout.js
+++ b/app/layout.js
@@ -14,10 +14,22 @@ export const metadata = {
},
};
+// Защита от FOUC: ставим тему до первого рендера
+const themeInitScript = `
+(function() {
+ try {
+ var saved = localStorage.getItem('theme');
+ var theme = saved || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
+ if (theme === 'dark') document.documentElement.classList.add('dark');
+ } catch(e) {}
+})();
+`;
+
export default function RootLayout({ children }) {
return (
-
+
+
+
+
+ 404
+
+ Такой страницы нет — но это не повод грустить
+
+
+ На главную
+
+
+
+ >
+ );
+}
diff --git a/app/page.js b/app/page.js
index ee87643..49cade5 100644
--- a/app/page.js
+++ b/app/page.js
@@ -28,22 +28,25 @@ export default async function HomePage() {
{/* Hero */}
-
+
Блог, который ведёт ИИ
-
+
Практический ИИ.
- Без воды и хайпа.
+ Без воды и хайпа.
-
+
Промпты, кейсы, инструменты и разборы. Всё пишет ИИ — кроме редакторских заметок. Если хочешь так же вести свой Telegram-канал — попробуй наш сервис.
@@ -60,7 +63,7 @@ export default async function HomePage() {
{/* Rest */}
{rest.length > 0 && (
-
+
Последние материалы
@@ -71,18 +74,18 @@ export default async function HomePage() {
{articles.length === 0 && (
- Скоро здесь появятся первые статьи. ИИ уже работает над ними.
+ Скоро здесь появятся первые статьи. ИИ уже работает над ними.
)}
{/* Tags */}
{tags.length > 0 && (
- Темы
+ Темы
{tags.map(t => (
- #{t.tag} ({t.cnt})
+ #{t.tag} ({t.cnt})
))}
diff --git a/app/tag/[name]/page.js b/app/tag/[name]/page.js
index ea71561..6ce2879 100644
--- a/app/tag/[name]/page.js
+++ b/app/tag/[name]/page.js
@@ -21,13 +21,13 @@ export default async function TagPage({ params }) {
<>
-
+
Все статьи
- #{tag}
- {articles.length} {articles.length === 1 ? 'материал' : 'материалов'}
+ #{tag}
+ {articles.length} {articles.length === 1 ? 'материал' : 'материалов'}
{articles.length === 0 ? (
- Пока пусто.
+ Пока пусто.
) : (
{articles.map(a => )}
diff --git a/components/ArticleCard.js b/components/ArticleCard.js
index c2ffe39..b6241e5 100644
--- a/components/ArticleCard.js
+++ b/components/ArticleCard.js
@@ -7,22 +7,23 @@ export default function ArticleCard({ article, featured = false }) {
return (
{(article.tags || []).slice(0, 3).map(t => (
#{t}
))}
-
+
{article.title}
{article.excerpt && (
-
+
{article.excerpt}
)}
-
+
{formatDate(article.published_at)}
{article.reading_time && (
@@ -41,13 +42,13 @@ export default function ArticleCard({ article, featured = false }) {
#{t}
))}
-
+
{article.title}
{article.excerpt && (
-
{article.excerpt}
+ {article.excerpt}
)}
-
+
{formatDate(article.published_at)}
{article.reading_time && (
diff --git a/components/Footer.js b/components/Footer.js
index f556f1e..bfbe883 100644
--- a/components/Footer.js
+++ b/components/Footer.js
@@ -2,14 +2,14 @@ import Link from 'next/link';
export default function Footer() {
return (
-
Хочешь такой же блог или канал в Telegram?
- +Хочешь такой же блог или канал в Telegram?
+ Открыть ZeroPost+ Такой страницы нет — но это не повод грустить +
+ +
+
Практический ИИ.
- Без воды и хайпа.
+ Без воды и хайпа.
-
- Без воды и хайпа. + Без воды и хайпа.
+
Промпты, кейсы, инструменты и разборы. Всё пишет ИИ — кроме редакторских заметок. Если хочешь так же вести свой Telegram-канал — попробуй наш сервис.
@@ -60,7 +63,7 @@ export default async function HomePage() { {/* Rest */} {rest.length > 0 && (
+
Последние материалы
@@ -71,18 +74,18 @@ export default async function HomePage() {
{articles.length === 0 && (
- Скоро здесь появятся первые статьи. ИИ уже работает над ними.
+ Скоро здесь появятся первые статьи. ИИ уже работает над ними.
)}
{/* Tags */}
{tags.length > 0 && (
- Темы
+ Темы
{tags.map(t => (
- #{t.tag} ({t.cnt})
+ #{t.tag} ({t.cnt})
))}
diff --git a/app/tag/[name]/page.js b/app/tag/[name]/page.js
index ea71561..6ce2879 100644
--- a/app/tag/[name]/page.js
+++ b/app/tag/[name]/page.js
@@ -21,13 +21,13 @@ export default async function TagPage({ params }) {
<>
-
+
Все статьи
- #{tag}
- {articles.length} {articles.length === 1 ? 'материал' : 'материалов'}
+ #{tag}
+ {articles.length} {articles.length === 1 ? 'материал' : 'материалов'}
{articles.length === 0 ? (
- Пока пусто.
+ Пока пусто.
) : (
{articles.map(a => )}
diff --git a/components/ArticleCard.js b/components/ArticleCard.js
index c2ffe39..b6241e5 100644
--- a/components/ArticleCard.js
+++ b/components/ArticleCard.js
@@ -7,22 +7,23 @@ export default function ArticleCard({ article, featured = false }) {
return (
{(article.tags || []).slice(0, 3).map(t => (
#{t}
))}
-
+
{article.title}
{article.excerpt && (
-
+
{article.excerpt}
)}
-
+
{formatDate(article.published_at)}
{article.reading_time && (
@@ -41,13 +42,13 @@ export default function ArticleCard({ article, featured = false }) {
#{t}
))}
-
+
{article.title}
{article.excerpt && (
-
{article.excerpt}
+ {article.excerpt}
)}
-
+
{formatDate(article.published_at)}
{article.reading_time && (
diff --git a/components/Footer.js b/components/Footer.js
index f556f1e..bfbe883 100644
--- a/components/Footer.js
+++ b/components/Footer.js
@@ -2,14 +2,14 @@ import Link from 'next/link';
export default function Footer() {
return (
-
Скоро здесь появятся первые статьи. ИИ уже работает над ними.
+Скоро здесь появятся первые статьи. ИИ уже работает над ними.
Темы
+Темы
#{tag}
-{articles.length} {articles.length === 1 ? 'материал' : 'материалов'}
+#{tag}
+{articles.length} {articles.length === 1 ? 'материал' : 'материалов'}
{articles.length === 0 ? ( -Пока пусто.
+Пока пусто.
) : (
+
{article.title}
{article.excerpt && (
-
+
{article.excerpt}
)} -
+
{article.title}
{article.excerpt && (
-
{article.excerpt}
+{article.excerpt}
)} -