Rounded Lift Gallery
Original · freeRounded image cards that lift with a soft shadow and reveal a label on hover.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/gallery-rounded-lift.json"use client";
import { useRef } from "react";
import { motion, useInView } from "motion/react";
type Shot = {
src: string;
alt: string;
title: string;
category: string;
location: string;
};
const SHOTS: Shot[] = [
{
src: "/img/gallery/g03.webp",
alt: "Morning fog drifting between dark evergreen ridgelines",
title: "Ridgeline at First Light",
category: "Landscape",
location: "Cascade Range, Oregon",
},
{
src: "/img/gallery/g11.webp",
alt: "Close portrait of a ceramicist's clay-dusted hands centering a pot",
title: "Centering the Clay",
category: "Craft",
location: "Studio No. 4, Lisbon",
},
{
src: "/img/gallery/g07.webp",
alt: "Neon signage reflected on a rain-soaked side street at night",
title: "Wet Neon, Empty Street",
category: "Street",
location: "Shinjuku, Tokyo",
},
{
src: "/img/gallery/g19.webp",
alt: "Overhead flat lay of espresso, a notebook and dried orange slices",
title: "The Nine A.M. Ritual",
category: "Still Life",
location: "Corner table, Turin",
},
{
src: "/img/gallery/g22.webp",
alt: "Sunlit dunes carved into long shadow-lined curves at dusk",
title: "Shadow & Sand",
category: "Landscape",
location: "Erg Chebbi, Morocco",
},
{
src: "/img/gallery/g14.webp",
alt: "Portrait of a dancer mid-turn with fabric caught in motion",
title: "Between Two Counts",
category: "Portrait",
location: "Rehearsal hall, Paris",
},
{
src: "/img/gallery/g28.webp",
alt: "Spiral concrete staircase photographed straight down the well",
title: "Descent",
category: "Architecture",
location: "Vatican Museums, Rome",
},
{
src: "/img/gallery/g05.webp",
alt: "Fishing boats moored in still turquoise water at golden hour",
title: "Low Tide, No Wind",
category: "Travel",
location: "Kotor Bay, Montenegro",
},
{
src: "/img/gallery/g31.webp",
alt: "Macro of morning dew beading along the edge of a green leaf",
title: "Overnight Rain",
category: "Macro",
location: "Backyard, Kyoto",
},
];
export default function GalleryRoundedLift() {
const headRef = useRef<HTMLDivElement>(null);
const headInView = useInView(headRef, { once: true, margin: "-80px" });
return (
<section className="relative w-full overflow-hidden bg-neutral-50 py-20 sm:py-28 dark:bg-neutral-950">
<style>{`
.grl-card {
animation: grl-fade-up 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes grl-fade-up {
from { opacity: 0; transform: translateY(26px) scale(0.985); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
.grl-card { animation: none !important; }
}
`}</style>
{/* soft ambient wash */}
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 -z-10 opacity-70"
>
<div className="absolute -left-24 top-10 h-72 w-72 rounded-full bg-indigo-200/40 blur-3xl dark:bg-indigo-500/10" />
<div className="absolute -right-16 bottom-0 h-80 w-80 rounded-full bg-emerald-200/40 blur-3xl dark:bg-emerald-500/10" />
</div>
<div className="mx-auto w-full max-w-7xl px-5 sm:px-8">
<div
ref={headRef}
className="mx-auto mb-12 max-w-2xl text-center sm:mb-16"
style={{
opacity: headInView ? 1 : 0,
transform: headInView ? "translateY(0)" : "translateY(18px)",
transition:
"opacity 0.7s cubic-bezier(0.22,1,0.36,1), transform 0.7s cubic-bezier(0.22,1,0.36,1)",
}}
>
<span className="inline-flex items-center gap-2 rounded-full border border-neutral-200 bg-white/70 px-3.5 py-1.5 text-xs font-medium tracking-wide text-neutral-600 shadow-sm backdrop-blur dark:border-neutral-800 dark:bg-neutral-900/70 dark:text-neutral-400">
<span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
Field Notes 2026
</span>
<h2 className="mt-5 text-balance text-3xl font-semibold tracking-tight text-neutral-900 sm:text-4xl md:text-5xl dark:text-neutral-50">
A year, in nine frames
</h2>
<p className="mt-4 text-pretty text-base leading-relaxed text-neutral-600 sm:text-lg dark:text-neutral-400">
Hand-picked stills from twelve months of wandering. Hover any card to
read where it was made.
</p>
</div>
<ul
role="list"
className="columns-1 gap-5 [column-fill:_balance] sm:columns-2 sm:gap-6 lg:columns-3"
>
{SHOTS.map((shot, i) => (
<li key={shot.src} className="mb-5 sm:mb-6 break-inside-avoid">
<motion.figure
tabIndex={0}
aria-label={`${shot.title} — ${shot.category}, ${shot.location}`}
className="grl-card group relative block cursor-pointer overflow-hidden rounded-[1.4rem] bg-neutral-200 shadow-[0_1px_2px_rgba(15,23,42,0.06)] outline-none ring-1 ring-neutral-900/5 transition-[transform,box-shadow] duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform hover:-translate-y-2 hover:shadow-[0_28px_50px_-18px_rgba(15,23,42,0.35)] focus-visible:-translate-y-2 focus-visible:shadow-[0_28px_50px_-18px_rgba(15,23,42,0.35)] focus-visible:ring-2 focus-visible:ring-indigo-500 dark:bg-neutral-800 dark:ring-white/10 dark:hover:shadow-[0_28px_55px_-18px_rgba(0,0,0,0.75)] dark:focus-visible:shadow-[0_28px_55px_-18px_rgba(0,0,0,0.75)]"
style={{ animationDelay: `${i * 70}ms` }}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={shot.src}
alt={shot.alt}
loading="lazy"
draggable={false}
className="block w-full select-none object-cover transition-transform duration-700 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:scale-[1.06] group-focus-visible:scale-[1.06]"
/>
{/* gradient scrim, fades in on hover */}
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 bg-gradient-to-t from-neutral-950/80 via-neutral-950/10 to-transparent opacity-0 transition-opacity duration-500 group-hover:opacity-100 group-focus-visible:opacity-100"
/>
{/* category chip, top-left */}
<span className="pointer-events-none absolute left-3.5 top-3.5 translate-y-1 rounded-full bg-white/85 px-2.5 py-1 text-[0.68rem] font-semibold uppercase tracking-wider text-neutral-800 opacity-0 shadow-sm backdrop-blur transition-all duration-500 group-hover:translate-y-0 group-hover:opacity-100 group-focus-visible:translate-y-0 group-focus-visible:opacity-100 dark:bg-neutral-900/85 dark:text-neutral-100">
{shot.category}
</span>
{/* label reveal, bottom */}
<figcaption className="pointer-events-none absolute inset-x-0 bottom-0 translate-y-3 p-4 opacity-0 transition-all duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:translate-y-0 group-hover:opacity-100 group-focus-visible:translate-y-0 group-focus-visible:opacity-100 sm:p-5">
<h3 className="text-lg font-semibold tracking-tight text-white drop-shadow-sm">
{shot.title}
</h3>
<p className="mt-0.5 flex items-center gap-1.5 text-sm text-neutral-200">
<svg
viewBox="0 0 24 24"
className="h-3.5 w-3.5 shrink-0"
fill="none"
stroke="currentColor"
strokeWidth={1.8}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z" />
<circle cx="12" cy="10" r="3" />
</svg>
{shot.location}
</p>
</figcaption>
</motion.figure>
</li>
))}
</ul>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Hover Caption Gallery
OriginalA responsive image grid where each tile subtly zooms and reveals a caption bar on hover.

Masonry Gallery
OriginalA Pinterest-style masonry gallery using CSS columns with varied-height images; tiles lift and brighten on hover.

Filterable Gallery
OriginalA filterable gallery with category tabs; clicking a tab animates the grid to show only that category.

Tilt Cards Gallery
OriginalA grid of image cards that tilt in 3D toward the cursor on hover, with a soft glare.

Lightbox Gallery
OriginalA thumbnail grid where clicking a thumb opens a full-screen lightbox with prev/next arrows and keyboard support.

Expanding Strip Gallery
OriginalA horizontal strip of image panels; hovering a panel expands it and shrinks the others to reveal its caption.

Marquee Gallery
OriginalTwo rows of images auto-scrolling in opposite directions, pausing on hover, with edge fade masks.

Hover Zoom Overlay Gallery
OriginalA grid where each image scales up under a dark gradient overlay revealing a title and a view button on hover.

Polaroid Stack Gallery
OriginalA scattered stack of slightly-rotated polaroid photos that straighten and lift on hover.
Featured Thumbs Gallery
OriginalA large featured image with a thumbnail row; selecting a thumbnail swaps the featured image with a crossfade.

Carousel Gallery
OriginalAn image carousel with arrows and dots, smooth slide transitions and autoplay that pauses on hover.

Duotone Hover Gallery
OriginalA grid where images sit under a coloured duotone wash that clears to full colour on hover.

