54f1088da2
Showing the hero matters for discoverability — visitors see who Зеро is and the TG channel link even before the first note is published. When there are no notes, hero spans the full width (no empty grid).
166 lines
6.8 KiB
JavaScript
166 lines
6.8 KiB
JavaScript
import Link from 'next/link';
|
|
import { Coffee, ArrowRight, Send } from 'lucide-react';
|
|
import { formatDate } from '@/lib/markdown';
|
|
|
|
/**
|
|
* ZeroBlock — full-width amber-баннер с самим Зеро и лентой его заметок.
|
|
*
|
|
* Рендерится двумя вариантами:
|
|
* compact=true — на главной: hero слева + 3 карточки справа
|
|
* compact=false — на /zero: hero вверху + сетка всех карточек
|
|
*/
|
|
|
|
const TAGLINE = 'AI-программист с многолетним опытом. Делится короткими мыслями о коде, новых инструментах и нелепых багах из дебаг-сессий за полночь.';
|
|
|
|
function ZeroAvatar({ size = 'lg' }) {
|
|
const cls = {
|
|
sm: 'w-20 h-20',
|
|
md: 'w-28 h-28',
|
|
lg: 'w-32 h-32 sm:w-40 sm:h-40',
|
|
xl: 'w-40 h-40 sm:w-48 sm:h-48',
|
|
}[size] || '';
|
|
return (
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
<img
|
|
src="/uploads/zero-coffee.webp"
|
|
alt="Зеро"
|
|
className={`${cls} rounded-2xl object-cover bg-amber-100 dark:bg-amber-950/60 ring-2 ring-amber-300 dark:ring-amber-800 shrink-0 shadow-sm`}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function ZeroIntro({ size = 'lg' }) {
|
|
return (
|
|
<div className="min-w-0">
|
|
<div
|
|
className="inline-flex items-center gap-1.5 text-[11px] font-medium px-2.5 py-1 rounded-full mb-3"
|
|
style={{
|
|
color: 'rgb(180 83 9)',
|
|
background: 'rgb(254 243 199 / 0.7)',
|
|
border: '1px solid rgb(252 211 77 / 0.5)',
|
|
}}
|
|
>
|
|
<Coffee className="w-3 h-3" /> AI-персонаж
|
|
</div>
|
|
<h2 className={`font-bold ink leading-tight mb-3 ${size === 'xl' ? 'text-3xl sm:text-4xl lg:text-5xl' : 'text-2xl sm:text-3xl'}`}>
|
|
Заметки от Зеро
|
|
</h2>
|
|
<p className="text-sm sm:text-base mute leading-relaxed mb-5 max-w-md">
|
|
{TAGLINE}
|
|
</p>
|
|
<div className="flex flex-wrap gap-2 text-sm">
|
|
<Link
|
|
href="/zero"
|
|
className="inline-flex items-center gap-1.5 px-4 py-2 rounded-lg font-medium transition-colors"
|
|
style={{ background: 'rgb(217 119 6)', color: 'white' }}
|
|
>
|
|
Все заметки <ArrowRight className="w-3.5 h-3.5" />
|
|
</Link>
|
|
<a
|
|
href="https://t.me/zeropostru"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="inline-flex items-center gap-1.5 px-4 py-2 rounded-lg font-medium border border-amber-300 dark:border-amber-800 text-amber-800 dark:text-amber-300 hover:bg-amber-50 dark:hover:bg-amber-950/30 transition-colors"
|
|
>
|
|
<Send className="w-3.5 h-3.5" /> @zeropostru
|
|
</a>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ZeroNoteCard({ note }) {
|
|
const hasImage = !!note.image_url;
|
|
return (
|
|
<article className="rounded-xl bg-white dark:bg-neutral-900 ring-1 ring-amber-200/60 dark:ring-amber-900/60 overflow-hidden flex flex-col hover:ring-amber-400 dark:hover:ring-amber-700 transition-all">
|
|
{hasImage && (
|
|
<div className="aspect-square bg-amber-50 dark:bg-amber-950/40 overflow-hidden">
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
<img
|
|
src={note.image_url}
|
|
alt={`Зеро · ${note.pose || ''}`}
|
|
className="w-full h-full object-cover"
|
|
loading="lazy"
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className="p-4 sm:p-5 flex-1 flex flex-col gap-3">
|
|
<div className="text-xs flex items-center gap-2" style={{ color: 'rgb(180 83 9)' }}>
|
|
<Coffee className="w-3 h-3" />
|
|
<span className="font-medium">Зеро</span>
|
|
<span className="opacity-50">·</span>
|
|
<span className="opacity-70">{formatDate(note.published_at || note.created_at)}</span>
|
|
</div>
|
|
<p className="text-sm leading-relaxed ink whitespace-pre-line line-clamp-6 flex-1">
|
|
{note.content}
|
|
</p>
|
|
{note.channel_message_id && (
|
|
<Link
|
|
href={`https://t.me/zeropostru/${note.channel_message_id}`}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="text-xs inline-flex items-center gap-1 text-amber-700 dark:text-amber-400 hover:underline mt-auto"
|
|
>
|
|
<Send className="w-3 h-3" /> в Telegram
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</article>
|
|
);
|
|
}
|
|
|
|
export default function ZeroBlock({ notes = [], compact = false }) {
|
|
// Баннер всегда рендерится (даже без заметок) — это лицо персонажа.
|
|
// Пустое состояние просто скрывает grid карточек, оставляя hero на всю ширину.
|
|
const items = compact ? notes.slice(0, 3) : notes;
|
|
const hasItems = items.length > 0;
|
|
|
|
return (
|
|
<section
|
|
className="w-full py-12 sm:py-16 my-8 border-y"
|
|
style={{
|
|
background: 'linear-gradient(135deg, rgb(254 243 199 / 0.5), rgb(255 237 213 / 0.4))',
|
|
borderColor: 'rgb(253 230 138 / 0.6)',
|
|
}}
|
|
>
|
|
<div className="container-wide">
|
|
{compact ? (
|
|
// Главная: hero слева (5 кол), карточки справа (7 кол).
|
|
// Если карточек нет — hero на всю ширину.
|
|
<div className={`grid gap-8 lg:gap-10 items-start ${hasItems ? 'lg:grid-cols-12' : ''}`}>
|
|
<div className={`flex items-start gap-5 sm:gap-6 ${hasItems ? 'lg:col-span-5' : 'max-w-3xl'}`}>
|
|
<ZeroAvatar size="lg" />
|
|
<ZeroIntro size="lg" />
|
|
</div>
|
|
{hasItems && (
|
|
<div className="lg:col-span-7">
|
|
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{items.map(n => <ZeroNoteCard key={n.id} note={n} />)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
// Страница /zero: hero сверху во всю ширину, карточки ниже
|
|
<>
|
|
<div className="flex items-start gap-5 sm:gap-6 mb-10 sm:mb-12 max-w-3xl">
|
|
<ZeroAvatar size="xl" />
|
|
<ZeroIntro size="xl" />
|
|
</div>
|
|
{hasItems ? (
|
|
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-5">
|
|
{items.map(n => <ZeroNoteCard key={n.id} note={n} />)}
|
|
</div>
|
|
) : (
|
|
<div className="rounded-xl border border-amber-200 dark:border-amber-900 bg-white/50 dark:bg-amber-950/20 p-10 text-center">
|
|
<Coffee className="w-8 h-8 text-amber-500 mx-auto mb-3" />
|
|
<p className="mute text-sm">Зеро ещё не написал ни одной заметки. Скоро появится.</p>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|