feat: светлая тема как основная + переключатель тем
- CSS-переменные --bg, --surface, --ink, --mute, --accent для обеих тем - darkMode: 'class' в Tailwind config - ThemeToggle компонент с Sun/Moon, сохраняет выбор в localStorage - Inline-скрипт в layout.js защищает от FOUC (FlashOfUnstyledContent) - Авто-определение по prefers-color-scheme как fallback - not-found.js: красивая 404 страница вместо дефолтной Next - Обновлены все компоненты и страницы — Header, Footer, ArticleCard, page.js, blog, tag, about
This commit is contained in:
+7
-4
@@ -1,6 +1,6 @@
|
||||
import Header from '@/components/Header';
|
||||
import Footer from '@/components/Footer';
|
||||
import { Sparkles, Cpu, BookOpen, Zap } from 'lucide-react';
|
||||
import { Sparkles } from 'lucide-react';
|
||||
|
||||
export const metadata = { title: 'О проекте' };
|
||||
|
||||
@@ -9,13 +9,16 @@ export default function AboutPage() {
|
||||
<>
|
||||
<Header />
|
||||
<main className="container-narrow pt-12 pb-16">
|
||||
<div className="inline-flex items-center gap-2 text-xs text-accent bg-accent/10 border border-accent/20 px-3 py-1.5 rounded-full mb-6">
|
||||
<div
|
||||
className="inline-flex items-center gap-2 text-xs accent px-3 py-1.5 rounded-full mb-6"
|
||||
style={{ background: 'rgb(var(--accent) / 0.1)', border: '1px solid rgb(var(--accent) / 0.2)' }}
|
||||
>
|
||||
<Sparkles className="w-3.5 h-3.5" /> О ZeroPost
|
||||
</div>
|
||||
<h1 className="text-4xl sm:text-5xl font-bold leading-tight mb-6">
|
||||
<h1 className="text-4xl sm:text-5xl font-bold leading-tight mb-6 ink">
|
||||
Эксперимент: блог, который ведёт ИИ
|
||||
</h1>
|
||||
<div className="prose prose-invert prose-lg max-w-none">
|
||||
<div className="prose prose-lg max-w-none">
|
||||
<p>
|
||||
ZeroPost — это два связанных проекта: <strong>публичный блог</strong>, который ты сейчас читаешь, и <strong>сервис</strong> для ведения Telegram-каналов с помощью ИИ.
|
||||
</p>
|
||||
|
||||
@@ -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 }) {
|
||||
<>
|
||||
<Header />
|
||||
<article className="container-narrow pt-10 pb-16">
|
||||
<Link href="/" className="btn-ghost text-sm mb-6 -ml-2">
|
||||
<Link href="/" className="btn btn-ghost text-sm mb-6 -ml-2">
|
||||
<ArrowLeft className="w-4 h-4" /> Все статьи
|
||||
</Link>
|
||||
|
||||
@@ -48,11 +47,11 @@ export default async function ArticlePage({ params }) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h1 className="font-serif text-3xl sm:text-5xl font-bold leading-tight mb-5 tracking-tight">
|
||||
<h1 className="font-serif text-3xl sm:text-5xl font-bold leading-tight mb-5 tracking-tight ink">
|
||||
{article.title}
|
||||
</h1>
|
||||
|
||||
<div className="flex items-center gap-4 text-sm text-mute pb-8 mb-8 border-b border-border/60">
|
||||
<div className="flex items-center gap-4 text-sm mute pb-8 mb-8 border-b-soft">
|
||||
<span>{article.author}</span>
|
||||
<span>·</span>
|
||||
<span>{formatDate(article.published_at)}</span>
|
||||
@@ -67,13 +66,13 @@ export default async function ArticlePage({ params }) {
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="prose prose-invert prose-lg max-w-none font-serif prose-headings:font-sans"
|
||||
className="prose prose-lg max-w-none font-serif prose-headings:font-sans"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
|
||||
<div className="mt-16 pt-8 border-t border-border/60 text-center">
|
||||
<p className="text-mute mb-4">Хочешь такой же блог или канал в Telegram?</p>
|
||||
<a href="https://app.zeropost.ru" className="btn-primary">
|
||||
<div className="mt-16 pt-8 border-t-soft text-center">
|
||||
<p className="mute mb-4">Хочешь такой же блог или канал в Telegram?</p>
|
||||
<a href="https://app.zeropost.ru" className="btn btn-primary">
|
||||
Открыть ZeroPost
|
||||
</a>
|
||||
</div>
|
||||
|
||||
+81
-7
@@ -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)); }
|
||||
}
|
||||
|
||||
+13
-1
@@ -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 (
|
||||
<html lang="ru">
|
||||
<html lang="ru" suppressHydrationWarning>
|
||||
<head>
|
||||
<script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
|
||||
<link
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import Link from 'next/link';
|
||||
import Header from '@/components/Header';
|
||||
import Footer from '@/components/Footer';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="container-narrow pt-20 pb-20 text-center">
|
||||
<div className="text-7xl font-bold mb-4 ink">404</div>
|
||||
<p className="text-xl mute mb-8">
|
||||
Такой страницы нет — но это не повод грустить
|
||||
</p>
|
||||
<Link href="/" className="btn btn-primary">
|
||||
<ArrowLeft className="w-4 h-4" /> На главную
|
||||
</Link>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
+13
-10
@@ -28,22 +28,25 @@ export default async function HomePage() {
|
||||
{/* Hero */}
|
||||
<section className="container-wide pt-12 pb-10 sm:pt-20 sm:pb-16">
|
||||
<div className="max-w-3xl">
|
||||
<div className="inline-flex items-center gap-2 text-xs text-accent bg-accent/10 border border-accent/20 px-3 py-1.5 rounded-full mb-6">
|
||||
<div
|
||||
className="inline-flex items-center gap-2 text-xs accent px-3 py-1.5 rounded-full mb-6"
|
||||
style={{ background: 'rgb(var(--accent) / 0.1)', border: '1px solid rgb(var(--accent) / 0.2)' }}
|
||||
>
|
||||
<Sparkles className="w-3.5 h-3.5" />
|
||||
Блог, который ведёт ИИ
|
||||
</div>
|
||||
<h1 className="text-4xl sm:text-6xl font-bold tracking-tight leading-tight mb-5">
|
||||
<h1 className="text-4xl sm:text-6xl font-bold tracking-tight leading-tight mb-5 ink">
|
||||
Практический ИИ.<br />
|
||||
<span className="text-mute">Без воды и хайпа.</span>
|
||||
<span className="mute">Без воды и хайпа.</span>
|
||||
</h1>
|
||||
<p className="text-lg text-mute mb-8 max-w-2xl">
|
||||
<p className="text-lg mute mb-8 max-w-2xl">
|
||||
Промпты, кейсы, инструменты и разборы. Всё пишет ИИ — кроме редакторских заметок. Если хочешь так же вести свой Telegram-канал — попробуй наш сервис.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Link href="#articles" className="btn-primary">
|
||||
<Link href="#articles" className="btn btn-primary">
|
||||
Читать статьи <ArrowRight className="w-4 h-4" />
|
||||
</Link>
|
||||
<a href="https://app.zeropost.ru" className="btn-ghost">
|
||||
<a href="https://app.zeropost.ru" className="btn btn-ghost">
|
||||
Получить ассистента
|
||||
</a>
|
||||
</div>
|
||||
@@ -60,7 +63,7 @@ export default async function HomePage() {
|
||||
{/* Rest */}
|
||||
{rest.length > 0 && (
|
||||
<section className="container-wide pb-12">
|
||||
<h2 className="text-sm font-medium uppercase tracking-widest text-mute mb-5">
|
||||
<h2 className="text-sm font-medium uppercase tracking-widest mute mb-5">
|
||||
Последние материалы
|
||||
</h2>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
@@ -71,18 +74,18 @@ export default async function HomePage() {
|
||||
|
||||
{articles.length === 0 && (
|
||||
<section className="container-wide py-20 text-center">
|
||||
<p className="text-mute">Скоро здесь появятся первые статьи. ИИ уже работает над ними.</p>
|
||||
<p className="mute">Скоро здесь появятся первые статьи. ИИ уже работает над ними.</p>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Tags */}
|
||||
{tags.length > 0 && (
|
||||
<section className="container-wide pb-12">
|
||||
<h2 className="text-sm font-medium uppercase tracking-widest text-mute mb-4">Темы</h2>
|
||||
<h2 className="text-sm font-medium uppercase tracking-widest mute mb-4">Темы</h2>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{tags.map(t => (
|
||||
<Link key={t.tag} href={`/tag/${encodeURIComponent(t.tag)}`} className="tag">
|
||||
#{t.tag} <span className="text-mute/60">({t.cnt})</span>
|
||||
#{t.tag} <span style={{ opacity: 0.6 }}>({t.cnt})</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -21,13 +21,13 @@ export default async function TagPage({ params }) {
|
||||
<>
|
||||
<Header />
|
||||
<main className="container-wide pt-10 pb-16">
|
||||
<Link href="/" className="btn-ghost text-sm mb-4 -ml-2">
|
||||
<Link href="/" className="btn btn-ghost text-sm mb-4 -ml-2">
|
||||
<ArrowLeft className="w-4 h-4" /> Все статьи
|
||||
</Link>
|
||||
<h1 className="text-3xl sm:text-4xl font-bold mb-2">#{tag}</h1>
|
||||
<p className="text-mute mb-8">{articles.length} {articles.length === 1 ? 'материал' : 'материалов'}</p>
|
||||
<h1 className="text-3xl sm:text-4xl font-bold mb-2 ink">#{tag}</h1>
|
||||
<p className="mute mb-8">{articles.length} {articles.length === 1 ? 'материал' : 'материалов'}</p>
|
||||
{articles.length === 0 ? (
|
||||
<p className="text-mute">Пока пусто.</p>
|
||||
<p className="mute">Пока пусто.</p>
|
||||
) : (
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
{articles.map(a => <ArticleCard key={a.id} article={a} />)}
|
||||
|
||||
@@ -7,22 +7,23 @@ export default function ArticleCard({ article, featured = false }) {
|
||||
return (
|
||||
<Link
|
||||
href={`/blog/${article.slug}`}
|
||||
className="article-card block group p-8 sm:p-10 border-accent/30 hover:border-accent"
|
||||
className="article-card block group p-8 sm:p-10"
|
||||
style={{ borderColor: 'rgb(var(--accent) / 0.3)' }}
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-2 mb-4">
|
||||
{(article.tags || []).slice(0, 3).map(t => (
|
||||
<span key={t} className="tag">#{t}</span>
|
||||
))}
|
||||
</div>
|
||||
<h2 className="text-2xl sm:text-3xl font-bold mb-3 group-hover:text-accent2 transition-colors leading-tight">
|
||||
<h2 className="text-2xl sm:text-3xl font-bold mb-3 leading-tight ink group-hover:accent transition-colors">
|
||||
{article.title}
|
||||
</h2>
|
||||
{article.excerpt && (
|
||||
<p className="text-mute text-base sm:text-lg leading-relaxed line-clamp-3 mb-4">
|
||||
<p className="mute text-base sm:text-lg leading-relaxed line-clamp-3 mb-4">
|
||||
{article.excerpt}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center gap-4 text-xs text-mute">
|
||||
<div className="flex items-center gap-4 text-xs mute">
|
||||
<span>{formatDate(article.published_at)}</span>
|
||||
{article.reading_time && (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
@@ -41,13 +42,13 @@ export default function ArticleCard({ article, featured = false }) {
|
||||
<span key={t} className="tag">#{t}</span>
|
||||
))}
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2 group-hover:text-accent2 transition-colors leading-snug">
|
||||
<h3 className="text-lg font-semibold mb-2 ink group-hover:accent transition-colors leading-snug">
|
||||
{article.title}
|
||||
</h3>
|
||||
{article.excerpt && (
|
||||
<p className="text-mute text-sm line-clamp-3 mb-4">{article.excerpt}</p>
|
||||
<p className="mute text-sm line-clamp-3 mb-4">{article.excerpt}</p>
|
||||
)}
|
||||
<div className="flex items-center gap-3 text-xs text-mute">
|
||||
<div className="flex items-center gap-3 text-xs mute">
|
||||
<span>{formatDate(article.published_at)}</span>
|
||||
{article.reading_time && (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
|
||||
@@ -2,14 +2,14 @@ import Link from 'next/link';
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-border/60 mt-20">
|
||||
<div className="container-wide py-10 flex flex-col sm:flex-row items-center justify-between gap-4 text-sm text-mute">
|
||||
<footer className="border-t-soft mt-20">
|
||||
<div className="container-wide py-10 flex flex-col sm:flex-row items-center justify-between gap-4 text-sm mute">
|
||||
<div>
|
||||
© {new Date().getFullYear()} ZeroPost — генерируется ИИ, читается людьми
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/about" className="hover:text-white transition-colors">О проекте</Link>
|
||||
<a href="https://app.zeropost.ru" className="hover:text-white transition-colors">Сервис</a>
|
||||
<Link href="/about" className="hover:ink transition-colors">О проекте</Link>
|
||||
<a href="https://app.zeropost.ru" className="hover:ink transition-colors">Сервис</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
+14
-10
@@ -1,22 +1,26 @@
|
||||
import Link from 'next/link';
|
||||
import { Sparkles, Github } from 'lucide-react';
|
||||
import { Sparkles } from 'lucide-react';
|
||||
import ThemeToggle from './ThemeToggle';
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<header className="border-b border-border/60 sticky top-0 z-20 bg-bg/85 backdrop-blur-md">
|
||||
<header className="sticky top-0 z-20 border-b-soft" style={{ background: 'rgb(var(--bg) / 0.85)', backdropFilter: 'saturate(180%) blur(12px)' }}>
|
||||
<div className="container-wide flex items-center justify-between py-4">
|
||||
<Link href="/" className="flex items-center gap-2 group">
|
||||
<div className="w-8 h-8 rounded-lg bg-accent/15 flex items-center justify-center group-hover:bg-accent/25 transition-colors">
|
||||
<Sparkles className="w-4 h-4 text-accent" />
|
||||
<div className="w-8 h-8 rounded-lg flex items-center justify-center transition-colors" style={{ background: 'rgb(var(--accent) / 0.12)' }}>
|
||||
<Sparkles className="w-4 h-4 accent" />
|
||||
</div>
|
||||
<span className="font-bold text-lg tracking-tight">ZeroPost</span>
|
||||
<span className="font-bold text-lg tracking-tight ink">ZeroPost</span>
|
||||
</Link>
|
||||
<nav className="flex items-center gap-1 sm:gap-4 text-sm">
|
||||
<Link href="/" className="btn-ghost text-sm py-1.5">Статьи</Link>
|
||||
<Link href="/about" className="btn-ghost text-sm py-1.5">О проекте</Link>
|
||||
<a href="https://app.zeropost.ru" className="btn-primary text-sm py-1.5">
|
||||
Открыть кабинет
|
||||
<nav className="flex items-center gap-1 sm:gap-2 text-sm">
|
||||
<Link href="/" className="btn btn-ghost text-sm py-1.5">Статьи</Link>
|
||||
<Link href="/about" className="btn btn-ghost text-sm py-1.5">О проекте</Link>
|
||||
<a href="https://app.zeropost.ru" className="btn btn-primary text-sm py-1.5 ml-1">
|
||||
Кабинет
|
||||
</a>
|
||||
<div className="ml-1">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Sun, Moon } from 'lucide-react';
|
||||
|
||||
export default function ThemeToggle() {
|
||||
const [theme, setTheme] = useState('light');
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
setTheme(isDark ? 'dark' : 'light');
|
||||
}, []);
|
||||
|
||||
function toggle() {
|
||||
const next = theme === 'dark' ? 'light' : 'dark';
|
||||
setTheme(next);
|
||||
if (next === 'dark') document.documentElement.classList.add('dark');
|
||||
else document.documentElement.classList.remove('dark');
|
||||
try { localStorage.setItem('theme', next); } catch {}
|
||||
}
|
||||
|
||||
// плейсхолдер до маунта, чтобы кнопка не прыгала
|
||||
if (!mounted) return <div className="w-9 h-9" aria-hidden />;
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggle}
|
||||
className="w-9 h-9 inline-flex items-center justify-center rounded-lg mute hover:ink hover:surface-2 transition-colors"
|
||||
title={theme === 'dark' ? 'Включить светлую тему' : 'Включить тёмную тему'}
|
||||
aria-label="Переключить тему"
|
||||
>
|
||||
{theme === 'dark' ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
+22
-20
@@ -1,5 +1,6 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
'./app/**/*.{js,jsx,ts,tsx}',
|
||||
'./components/**/*.{js,jsx,ts,tsx}',
|
||||
@@ -7,14 +8,15 @@ module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
bg: '#0a0a0a',
|
||||
surface: '#141414',
|
||||
surface2: '#1c1c1c',
|
||||
border: '#2a2a2a',
|
||||
accent: '#10b981',
|
||||
accent2: '#34d399',
|
||||
ink: '#e5e7eb',
|
||||
mute: '#9ca3af',
|
||||
// Используем CSS-переменные напрямую — для совместимости со старым кодом
|
||||
bg: 'rgb(var(--bg) / <alpha-value>)',
|
||||
surface: 'rgb(var(--surface) / <alpha-value>)',
|
||||
surface2: 'rgb(var(--surface-2) / <alpha-value>)',
|
||||
border: 'rgb(var(--border) / <alpha-value>)',
|
||||
ink: 'rgb(var(--ink) / <alpha-value>)',
|
||||
mute: 'rgb(var(--mute) / <alpha-value>)',
|
||||
accent: 'rgb(var(--accent) / <alpha-value>)',
|
||||
accent2: 'rgb(var(--accent-2) / <alpha-value>)',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
@@ -23,18 +25,18 @@ module.exports = {
|
||||
typography: ({ theme }) => ({
|
||||
DEFAULT: {
|
||||
css: {
|
||||
'--tw-prose-body': theme('colors.ink'),
|
||||
'--tw-prose-headings': '#ffffff',
|
||||
'--tw-prose-links': theme('colors.accent2'),
|
||||
'--tw-prose-bold': '#ffffff',
|
||||
'--tw-prose-quotes': theme('colors.mute'),
|
||||
'--tw-prose-quote-borders': theme('colors.accent'),
|
||||
'--tw-prose-code': theme('colors.accent2'),
|
||||
'--tw-prose-pre-bg': theme('colors.surface2'),
|
||||
'--tw-prose-pre-code': theme('colors.ink'),
|
||||
'--tw-prose-bullets': theme('colors.mute'),
|
||||
'--tw-prose-counters': theme('colors.mute'),
|
||||
'--tw-prose-hr': theme('colors.border'),
|
||||
'--tw-prose-body': 'rgb(var(--ink))',
|
||||
'--tw-prose-headings': 'rgb(var(--ink))',
|
||||
'--tw-prose-links': 'rgb(var(--accent-2))',
|
||||
'--tw-prose-bold': 'rgb(var(--ink))',
|
||||
'--tw-prose-quotes': 'rgb(var(--mute))',
|
||||
'--tw-prose-quote-borders': 'rgb(var(--accent))',
|
||||
'--tw-prose-code': 'rgb(var(--accent-2))',
|
||||
'--tw-prose-pre-bg': 'rgb(var(--surface-2))',
|
||||
'--tw-prose-pre-code': 'rgb(var(--ink))',
|
||||
'--tw-prose-bullets': 'rgb(var(--mute))',
|
||||
'--tw-prose-counters': 'rgb(var(--mute))',
|
||||
'--tw-prose-hr': 'rgb(var(--border))',
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user