Event Detail page

This commit is contained in:
2026-09-25 11:12:15 +05:30
parent 8a75cff501
commit 3773b3c1ed
+44 -12
View File
@@ -7,11 +7,33 @@ import Reveal from "./Reveal";
import EmptyNote from "./EmptyNote";
import type { GalleryPhoto } from "@/lib/content";
const CARD =
"relative h-52 w-72 shrink-0 sm:h-64 sm:w-[25rem] [perspective:1200px] cursor-pointer text-left outline-none";
const CARD_BASE = "relative [perspective:1200px] cursor-pointer text-left outline-none";
/** strip = fixed size for the scrolling row; grid = fills its grid cell at a 4:3 ratio. */
const CARD_SIZE = {
strip: "h-56 w-[18rem] shrink-0 sm:h-64 sm:w-[22rem]",
grid: "w-full aspect-[4/3]",
} as const;
/** Still row (4 or fewer photos): one row on desktop, sized so it never wraps. */
const GRID_LAYOUT: Record<number, string> = {
1: "max-w-md grid-cols-1",
2: "max-w-4xl grid-cols-1 sm:grid-cols-2",
3: "max-w-6xl grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
4: "max-w-7xl grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
};
/** One photo card: photo on the front, details on the back (flips on hover). Click opens the viewer. */
function FlipCard({ photo, onOpen, hidden }: { photo: GalleryPhoto; onOpen: () => void; hidden?: boolean }) {
function FlipCard({
photo,
onOpen,
hidden,
size = "strip",
}: {
photo: GalleryPhoto;
onOpen: () => void;
hidden?: boolean;
size?: keyof typeof CARD_SIZE;
}) {
const hasDetails = Boolean(photo.alt || photo.description || photo.date);
return (
<button
@@ -20,7 +42,7 @@ function FlipCard({ photo, onOpen, hidden }: { photo: GalleryPhoto; onOpen: () =
aria-hidden={hidden}
tabIndex={hidden ? -1 : 0}
aria-label={photo.alt ? `View photo: ${photo.alt}` : "View photo"}
className={`group/card ${CARD}`}
className={`group/card ${CARD_BASE} ${CARD_SIZE[size]}`}
>
<div
className={`relative h-full w-full rounded-2xl shadow-[0_8px_24px_-12px_rgba(11,37,69,0.35)] transition-transform duration-700 [transform-style:preserve-3d] ${
@@ -30,14 +52,24 @@ function FlipCard({ photo, onOpen, hidden }: { photo: GalleryPhoto; onOpen: () =
}`}
>
{/* front */}
<div className="absolute inset-0 overflow-hidden rounded-2xl bg-rotary-paper ring-1 ring-rotary-navy/[0.06] [backface-visibility:hidden]">
<div className="absolute inset-0 overflow-hidden rounded-2xl bg-rotary-navy ring-1 ring-rotary-navy/[0.06] [backface-visibility:hidden]">
{/* canvas: a blurred copy fills the frame, so portrait and landscape photos both fit without cropping */}
<Image
src={photo.src}
alt=""
aria-hidden
fill
sizes="(min-width: 640px) 360px, 288px"
unoptimized={photo.src.startsWith("/uploads/")}
className="scale-125 object-cover opacity-70 blur-2xl"
/>
<Image
src={photo.src}
alt={hidden ? "" : photo.alt || "Rotary Club of Vellore photo"}
fill
sizes="(min-width: 640px) 400px, 288px"
sizes="(min-width: 1024px) 360px, (min-width: 640px) 50vw, 100vw"
unoptimized={photo.src.startsWith("/uploads/")}
className="object-cover object-top"
className="object-contain drop-shadow-[0_4px_16px_rgba(0,0,0,0.25)]"
/>
{photo.alt && (
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-rotary-navy/85 to-transparent px-4 pb-3 pt-10 [@media(hover:hover)]:hidden">
@@ -169,8 +201,8 @@ function Lightbox({
export default function Gallery({ photos }: { photos: GalleryPhoto[] }) {
const [open, setOpen] = useState<number | null>(null);
// Up to 3 photos: a still, centered row. More than 3: auto-scroll.
const slide = photos.length > 3;
// Up to 4 photos: a still row. More than 4: auto-scroll.
const slide = photos.length > 4;
// Repeat short lists so the strip is always wider than the screen, then
// duplicate it so the loop is seamless (animation moves -50%).
const base = slide ? Array.from({ length: Math.ceil(8 / photos.length) }, () => photos).flat() : photos;
@@ -187,9 +219,9 @@ export default function Gallery({ photos }: { photos: GalleryPhoto[] }) {
</div>
{photos.length > 0 && !slide && (
<div className="mx-auto mt-8 flex max-w-7xl flex-wrap justify-center gap-6 px-4 sm:px-6 lg:px-8">
<div className={`mx-auto mt-8 grid gap-5 px-4 sm:px-6 lg:px-8 ${GRID_LAYOUT[photos.length]}`}>
{photos.map((p, i) => (
<FlipCard key={p.id} photo={p} onOpen={() => setOpen(i)} />
<FlipCard key={p.id} size="grid" photo={p} onOpen={() => setOpen(i)} />
))}
</div>
)}
@@ -201,7 +233,7 @@ export default function Gallery({ photos }: { photos: GalleryPhoto[] }) {
style={{ animationDuration: `${base.length * 5}s` }}
>
{loop.map((p, i) => (
<div key={`${p.id}-${i}`} className="mr-6">
<div key={`${p.id}-${i}`} className="mr-5">
<FlipCard photo={p} hidden={i >= photos.length} onOpen={() => setOpen(i % photos.length)} />
</div>
))}