179 lines
5.6 KiB
TypeScript
179 lines
5.6 KiB
TypeScript
'use client';
|
||
|
||
import Link from 'next/link';
|
||
import { motion } from 'framer-motion';
|
||
import { useLocale } from '@/lib/i18n/locale-context';
|
||
import type { PostContent, Block } from '@/lib/content/posts';
|
||
import { cn } from '@/lib/utils';
|
||
|
||
const FA_DIGITS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'] as const;
|
||
const toFa = (s: string | number) =>
|
||
s.toString().replace(/\d/g, (d) => FA_DIGITS[Number(d)]);
|
||
|
||
type Meta = { title: string; category: string; readTime: number };
|
||
|
||
const ACCENT_TEXT: Record<PostContent['accent'], string> = {
|
||
electric: 'text-electric',
|
||
violet: 'text-violet',
|
||
magenta: 'text-magenta',
|
||
emerald: 'text-emerald',
|
||
cyan: 'text-cyan',
|
||
};
|
||
const ACCENT_BORDER: Record<PostContent['accent'], string> = {
|
||
electric: 'border-electric/30 bg-electric/5 text-electric',
|
||
violet: 'border-violet/30 bg-violet/5 text-violet',
|
||
magenta: 'border-magenta/30 bg-magenta/5 text-magenta',
|
||
emerald: 'border-emerald/30 bg-emerald/5 text-emerald',
|
||
cyan: 'border-cyan/30 bg-cyan/5 text-cyan',
|
||
};
|
||
|
||
export function BlogArticle({
|
||
meta,
|
||
content,
|
||
}: {
|
||
meta: { fa: Meta; en: Meta };
|
||
content: PostContent;
|
||
}) {
|
||
const { t, locale } = useLocale();
|
||
const m = meta[locale];
|
||
const body = content[locale];
|
||
const dir = locale === 'fa' ? 'rtl' : 'ltr';
|
||
|
||
const dateLabel = new Intl.DateTimeFormat(
|
||
locale === 'fa' ? 'fa-IR' : 'en-US',
|
||
{ year: 'numeric', month: 'long', day: 'numeric' },
|
||
).format(new Date(content.date));
|
||
|
||
return (
|
||
<article dir={dir} className="relative px-5 py-32 sm:px-8">
|
||
{/* Cover glow */}
|
||
<div
|
||
aria-hidden
|
||
className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-80 bg-radial-aurora opacity-60"
|
||
/>
|
||
|
||
<div className="mx-auto max-w-3xl">
|
||
<Link
|
||
href="/#blog"
|
||
className="label-mono inline-flex items-center gap-2 text-slate-400 transition-colors hover:text-electric"
|
||
>
|
||
<span className={locale === 'fa' ? 'rotate-180' : ''}>←</span>
|
||
{t.nav.blog}
|
||
</Link>
|
||
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
|
||
>
|
||
<div className="mt-7 flex flex-wrap items-center gap-3">
|
||
<span
|
||
className={cn(
|
||
'rounded-full border px-2.5 py-0.5 font-mono text-[0.65rem] uppercase tracking-wider',
|
||
ACCENT_BORDER[content.accent],
|
||
)}
|
||
>
|
||
{m.category}
|
||
</span>
|
||
<span className="font-mono text-[0.7rem] text-slate-500">
|
||
{dateLabel}
|
||
</span>
|
||
<span className="font-mono text-[0.7rem] text-slate-500">
|
||
{locale === 'fa' ? toFa(m.readTime) : m.readTime}{' '}
|
||
{t.blog.readTimeSuffix}
|
||
</span>
|
||
</div>
|
||
|
||
<h1
|
||
className={cn(
|
||
'mt-5 font-display text-[clamp(2rem,4.5vw,3.2rem)] font-extrabold leading-[1.08] tracking-tight text-white',
|
||
locale === 'fa' && 'font-fa',
|
||
)}
|
||
>
|
||
{m.title}
|
||
</h1>
|
||
|
||
<p className="mt-6 text-balance text-[clamp(1.05rem,1.8vw,1.3rem)] leading-relaxed text-slate-300">
|
||
{body.lead}
|
||
</p>
|
||
</motion.div>
|
||
|
||
{/* Body */}
|
||
<div className="mt-12 flex flex-col gap-6">
|
||
{body.blocks.map((block, i) => (
|
||
<BlockView key={i} block={block} accent={content.accent} />
|
||
))}
|
||
</div>
|
||
|
||
{/* CTA */}
|
||
<div className="mt-16 border-t border-white/5 pt-10">
|
||
<p className="text-slate-400">
|
||
{locale === 'fa'
|
||
? 'این موضوع به سیستم شما مربوط است؟ بیایید دربارهاش صحبت کنیم.'
|
||
: 'Is this relevant to your system? Let’s talk it through.'}
|
||
</p>
|
||
<div className="mt-5 flex flex-wrap gap-3">
|
||
<Link href="/#contact" className="btn-primary">
|
||
{t.hero.ctaPrimary}
|
||
</Link>
|
||
<Link href="/#blog" className="btn-ghost">
|
||
{t.nav.blog}
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
);
|
||
}
|
||
|
||
function BlockView({
|
||
block,
|
||
accent,
|
||
}: {
|
||
block: Block;
|
||
accent: PostContent['accent'];
|
||
}) {
|
||
switch (block.k) {
|
||
case 'h2':
|
||
return (
|
||
<h2 className="mt-4 font-display text-[clamp(1.3rem,2.4vw,1.7rem)] font-semibold leading-snug text-white">
|
||
{block.t}
|
||
</h2>
|
||
);
|
||
case 'p':
|
||
return (
|
||
<p className="text-[1.02rem] leading-[1.85] text-slate-300">
|
||
{block.t}
|
||
</p>
|
||
);
|
||
case 'ul':
|
||
return (
|
||
<ul className="flex flex-col gap-2.5">
|
||
{block.items.map((it, i) => (
|
||
<li key={i} className="flex gap-3 text-[1.02rem] leading-relaxed text-slate-300">
|
||
<span className={cn('mt-2 h-1.5 w-1.5 shrink-0 rounded-full', `bg-current ${ACCENT_TEXT[accent]}`)} />
|
||
<span>{it}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
);
|
||
case 'quote':
|
||
return (
|
||
<blockquote
|
||
className={cn(
|
||
'my-2 border-s-2 ps-5 text-[1.1rem] font-medium italic leading-relaxed text-slate-200',
|
||
accent === 'magenta' ? 'border-magenta' : 'border-electric',
|
||
)}
|
||
>
|
||
{block.t}
|
||
</blockquote>
|
||
);
|
||
case 'code':
|
||
return (
|
||
<pre className="overflow-x-auto rounded-xl border border-white/10 bg-base-900/80 p-4 font-mono text-[0.85rem] leading-relaxed text-slate-200">
|
||
<code>{block.t}</code>
|
||
</pre>
|
||
);
|
||
}
|
||
}
|