Event Detail page
This commit is contained in:
+44
-12
@@ -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>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user