first commit
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user