# Gallery 2

> gallery

A shadcn/ui block from the [@cnippet-blocks](https://blocks.cnippet.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cnippet-blocks/gallery-2).

- Page, with a live preview: https://shadcn.exchange/r/cnippet-blocks/gallery-2
- Screenshot: https://shadcn.exchange/shot/cnippet-blocks/gallery-2/light.png
- Registry item: https://blocks.cnippet.dev/r/gallery-2.json
- Categories: gallery
- Listed: 2026-11-01, source last changed 2026-11-01

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/cnippet-blocks/gallery-2.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@cnippet/utils`

## Files

### `registry/default/sections/gallery-2.tsx`

```tsx
"use client";

import { Marquee } from "@/components/ui/marquee";
import { cn } from "@/lib/utils";

const galleryRow1 = [
  {
    id: "1",
    src: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=600&h=400&fit=crop",
    width: "narrow",
  },
  {
    id: "2",
    src: "https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=800&h=500&fit=crop",
    width: "wide",
  },
  {
    id: "3",
    src: "https://images.unsplash.com/photo-1500382017468-9049fed747ef?w=700&h=450&fit=crop",
    width: "medium",
  },
  {
    id: "4",
    src: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=600&h=400&fit=crop",
    width: "narrow",
  },
];

const galleryRow2 = [
  {
    id: "5",
    src: "https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=500&h=600&fit=crop",
    width: "narrow",
  },
  {
    id: "6",
    src: "https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=900&h=500&fit=crop",
    width: "wide",
  },
  {
    id: "7",
    src: "https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=700&h=500&fit=crop",
    width: "medium",
  },
  {
    id: "8",
    src: "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=500&h=600&fit=crop",
    width: "narrow",
  },
];

function GalleryImage({
  image,
  className,
}: {
  image: (typeof galleryRow1)[0];
  className?: string;
}) {
  return (
    <div
      className={cn(
        "relative overflow-hidden rounded-xl",
        image.width === "wide"
          ? "w-96"
          : image.width === "medium"
            ? "w-72"
            : "w-56",
        "h-52",
        className,
      )}
    >
      <img
        alt="Gallery"
        className="h-full w-full object-cover grayscale transition-all duration-500 hover:grayscale-0"
        src={image.src}
      />
    </div>
  );
}

export function Gallery2() {
  return (
    <section className="w-full overflow-hidden bg-gray-900 py-16 md:py-20 dark:bg-black">
      <div className="mx-auto mb-12 w-full max-w-6xl px-6">
        {/* Header - Two column */}
        <div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
          {/* Left */}
          <div>
            <span className="mb-4 block font-medium text-sm text-white/70">
              [Gallery]
            </span>

            <h2 className="font-normal text-3xl text-white leading-tight md:text-4xl lg:text-5xl">
              Moments That Move
              <br />
              the Planet Forward
            </h2>
          </div>

          {/* Right */}
          <div className="flex items-end lg:justify-end">
            <p className="max-w-sm text-sm text-white/80 sm:text-base">
              A visual journey of our commitment to creating a positive impact
              for the planet and future generations.
            </p>
          </div>
        </div>
      </div>

      {/* Gallery Rows */}
      <div className="space-y-4">
        {/* Row 1 - Scrolls left */}
        <Marquee className="[--duration:50s] [--gap:1rem]" pauseOnHover>
          {galleryRow1.map((image) => (
            <GalleryImage image={image} key={image.id} />
          ))}
        </Marquee>

        {/* Row 2 - Scrolls right (reverse) */}
        <Marquee className="[--duration:55s] [--gap:1rem]" pauseOnHover reverse>
          {galleryRow2.map((image) => (
            <GalleryImage image={image} key={image.id} />
          ))}
        </Marquee>
      </div>
    </section>
  );
}

export default Gallery2;
```

### `components/ui/marquee.tsx`

```tsx
//biome-ignore-all lint/suspicious/noArrayIndexKey:<>

import type { ComponentPropsWithoutRef } from "react";
import { cn } from "@/lib/utils";

interface MarqueeProps extends ComponentPropsWithoutRef<"div"> {
  className?: string;
  reverse?: boolean;
  pauseOnHover?: boolean;
  children: React.ReactNode;
  vertical?: boolean;
  repeat?: number;
}

export function Marquee({
  className,
  reverse = false,
  pauseOnHover = false,
  children,
  vertical = false,
  repeat = 4,
  ...props
}: MarqueeProps) {
  return (
    <div
      {...props}
      className={cn(
        "group flex gap-(--gap) overflow-hidden p-2 [--duration:40s] [--gap:1rem]",
        {
          "flex-col": vertical,
          "flex-row": !vertical,
        },
        className,
      )}
    >
      {Array(repeat)
        .fill(0)
        .map((_, i) => (
          <div
            className={cn("flex shrink-0 justify-around gap-(--gap)", {
              "animate-marquee flex-row": !vertical,
              "animate-marquee-vertical flex-col": vertical,
              "direction-[reverse]": reverse,
              "group-hover:paused": pauseOnHover,
            })}
            key={i}
          >
            {children}
          </div>
        ))}
    </div>
  );
}
```
