"use client"; /** * PostGridCard — square card used in the 2-per-row grid view. * * • With image: first attached image fills the card; a bottom-up dark * gradient makes the caption legible without dropping the image's * atmosphere. * • Without image: a palette-aware Tailwind gradient stands in; the * caption sits as a serif quote bottom-left in the foreground colour. * * `onDelete` shows a small X in the corner for owner views. Used both for * actual post deletion (on /u/me) and for removing from a collection * (on /u/me/collections/[id]); the parent decides which. */ import { useState } from "react"; import Image from "next/image"; import Link from "next/link"; import { ImageIcon, X } from "lucide-react"; import { cn } from "@/lib/utils"; import type { Post } from "@/lib/social"; import { displaySourceLabel } from "@/lib/source-reference"; import { useMediaQuery } from "@/components/selection-toolbar/shared"; import { postSourceHref, quoteCardBgStyle, quoteSize } from "./post-card-shared"; import { UpdateBackgroundModal } from "./update-background-modal"; export function PostGridCard({ post, onDelete, onUpdated, href: hrefProp, }: { post: Post; onDelete?: (id: string) => void; /** Owner-only: shown when defined so the user can swap/remove the * card's background image. */ onUpdated?: (updated: Post) => void; /** Override the click target. Defaults to the source-passage URL. * Profile grids pass a `/u//posts#post-` anchor so the * thumbnail jumps to the Instagram-style scroll feed instead of the * knowledgebase page. */ href?: string; }) { const isMobile = useMediaQuery("(max-width: 767px)"); const [bgModal, setBgModal] = useState(false); const firstImage = post.images?.[0]?.url; const href = hrefProp ?? postSourceHref(post); const size = quoteSize(post.caption, "grid"); // Image variant always anchors text bottom-left over the image's dark // overlay. No-image variant lets `size.alignment` decide — short quotes // sit centred so the gradient frames them; long quotes fall back to // bottom-left so they don't cover the whole card. const bottomLeft = !!firstImage || size.alignment === "bottom-left"; return (
{firstImage ? ( <> {/* Full-coverage scrim — even the top of the card carries enough darkness (~40%) so white text overlaid anywhere stays legible without looking blended with the image. */}
) : (
)}

“{post.caption}”

{displaySourceLabel(post.source_url, post.source_title)}

{onUpdated || onDelete ? (
{onUpdated ? ( ) : null} {onDelete ? ( ) : null}
) : null} {onUpdated ? ( setBgModal(false)} post={post} isMobile={isMobile} onUpdated={onUpdated} /> ) : null}
); }