89 lines
2.9 KiB
TypeScript
89 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* CollectionGridCard — square collection card for the 2-per-row grid view.
|
|
*
|
|
* • With cover: cover fills the square; bottom-up overlay for legibility.
|
|
* • Without cover: the same flat palette-aware surface that post cards
|
|
* fall back to — pure white on light themes, palette-specific dark
|
|
* contrast on dark themes (see `--quote-card-bg`).
|
|
*
|
|
* Visibility badge (Globe/Lock) sits top-right. Title + count read
|
|
* bottom-centre in white over the cover image's dark overlay; on the
|
|
* flat fallback they use `text-foreground` so they invert cleanly with
|
|
* the theme.
|
|
*/
|
|
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { Globe, Lock } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import type { Collection } from "@/lib/social";
|
|
import { quoteCardBgStyle } from "./post-card-shared";
|
|
|
|
export function CollectionGridCard({
|
|
collection: c,
|
|
href,
|
|
}: {
|
|
collection: Collection;
|
|
href: string;
|
|
}) {
|
|
const hasCover = !!c.cover_url;
|
|
return (
|
|
<Link
|
|
href={href}
|
|
className="group relative block aspect-square rounded-2xl overflow-hidden ring-1 ring-foreground/10 hover:ring-foreground/25 hover:shadow-md transition-all select-none"
|
|
>
|
|
{hasCover ? (
|
|
<>
|
|
<Image
|
|
src={c.cover_url!}
|
|
alt=""
|
|
fill
|
|
sizes="(max-width: 640px) 50vw, 320px"
|
|
className="object-cover"
|
|
unoptimized
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/30 to-transparent" />
|
|
</>
|
|
) : (
|
|
<div className="absolute inset-0" style={quoteCardBgStyle()} />
|
|
)}
|
|
|
|
{/* Visibility badge — black pill works on both image overlay and
|
|
flat surface (white or dark). */}
|
|
{c.is_public ? null : (
|
|
<div className="absolute top-3 right-3 inline-flex items-center gap-1 rounded-full bg-black/40 backdrop-blur-sm px-2.5 py-1 text-[0.6rem] uppercase tracking-[0.18em] font-medium text-white">
|
|
<Lock className="size-3" />
|
|
{"Private"}
|
|
</div>
|
|
)}
|
|
|
|
{/* Title + count — text colour follows the surface beneath. */}
|
|
<div
|
|
className={cn(
|
|
"absolute inset-0 flex flex-col items-center justify-center text-center px-5 sm:px-7",
|
|
hasCover ? "text-white" : "text-foreground",
|
|
)}
|
|
>
|
|
<p
|
|
className={cn(
|
|
"font-[family-name:var(--font-title)] text-lg sm:text-2xl font-semibold leading-snug line-clamp-4 break-words",
|
|
hasCover && "drop-shadow-sm",
|
|
)}
|
|
>
|
|
{c.title}
|
|
</p>
|
|
<p
|
|
className={cn(
|
|
"mt-2 text-[0.65rem] uppercase tracking-[0.22em] font-medium",
|
|
hasCover ? "text-white/85" : "text-muted-foreground",
|
|
)}
|
|
>
|
|
{c.item_count} post{c.item_count === 1 ? "" : "s"}
|
|
</p>
|
|
</div>
|
|
</Link>
|
|
);
|
|
}
|