Archived
MapleBrief: production Canadian news aggregator
Next.js 14 + TypeScript + Tailwind + Prisma/SQLite. - LLM synthesis abstraction (OpenAI/Anthropic/Ollama) + admin settings UI - RSS ingestion pipeline (parser + cheerio content) + node-cron worker - AdSense AdUnit placements (header/in-feed/in-article/sidebar) - Sources analysis attribution, nofollow links, canonical/OG, sitemap/robots - Admin auth (ADMIN_API_KEY, timingSafeEqual, fail-closed 401) - Multi-cell lady-ga-ga marker, sitemap, robots, legal pages - Comprehensive README (setup, LLM config, AdSense, migrations, deploy)
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import Link from 'next/link';
|
||||
import type { ArticleCard as Card } from '@/lib/queries';
|
||||
import { formatRelativeTime } from '@/lib/format';
|
||||
|
||||
export default function ArticleCard({ card }: { card: Card }) {
|
||||
const href = `/article/${card.slug}`;
|
||||
const title = card.headline ?? card.title;
|
||||
return (
|
||||
<article className="group flex flex-col overflow-hidden rounded-xl border border-ink-200 bg-white shadow-sm transition hover:-translate-y-0.5 hover:shadow-md">
|
||||
<Link
|
||||
href={href}
|
||||
className="relative block aspect-[16/9] overflow-hidden bg-ink-100"
|
||||
>
|
||||
{card.image ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
src={card.image}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
className="h-full w-full object-cover transition duration-300 group-hover:scale-[1.03]"
|
||||
/>
|
||||
) : (
|
||||
<span className="flex h-full items-center justify-center font-display text-4xl text-ink-300">
|
||||
{card.siteName.charAt(0)}
|
||||
</span>
|
||||
)}
|
||||
<span className="absolute left-3 top-3 rounded bg-ink-950/80 px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-white">
|
||||
{card.siteName}
|
||||
</span>
|
||||
</Link>
|
||||
<div className="flex flex-1 flex-col p-4">
|
||||
<h3 className="font-display text-lg font-bold leading-snug text-ink-900 group-hover:text-maple-700">
|
||||
<Link href={href}>{title}</Link>
|
||||
</h3>
|
||||
<p className="mt-2 flex-1 text-sm leading-relaxed text-ink-600">
|
||||
{card.excerpt}
|
||||
</p>
|
||||
<div className="mt-3 flex items-center justify-between text-xs text-ink-500">
|
||||
<span>{card.publishedAt ? formatRelativeTime(card.publishedAt) : 'recent'}</span>
|
||||
<span className="flex gap-1">
|
||||
{card.tags.slice(0, 2).map((t) => (
|
||||
<Link
|
||||
key={t}
|
||||
href={`/tag/${t}`}
|
||||
className="rounded bg-ink-100 px-1.5 py-0.5 hover:bg-maple-100 hover:text-maple-800"
|
||||
>
|
||||
{t}
|
||||
</Link>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user