# Logo cloud 5

> Three-column vertical logo drift with alternating scroll direction, pause on hover, and brand wordmarks.

A shadcn/ui block from the [@nusaiba](https://nusaiba.dev) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/nusaiba/logo-cloud-5).

- Page, with a live preview: https://shadcn.exchange/r/nusaiba/logo-cloud-5
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/nusaiba/logo-cloud-5-light-93feea0703a71d3d.png
- Registry item: https://nusaiba.dev/r/base-nova/logo-cloud-5.json
- Categories: logo-cloud
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://nusaiba.dev/r/base-nova/logo-cloud-5.json
```

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

## Dependencies

- npm packages: `motion`
- Registry items: `@nusaiba/foundation`, `@nusaiba/brand-typography`

## Files

### `registry/blocks/logo-cloud/5/logo-cloud-section.tsx`

```tsx
"use client";

import { useState } from "react";
import { motion, useReducedMotion } from "motion/react";
import { BrandFonts, BrandWordmark } from "@/lib/brand-typography";
import { cn } from "@/lib/utils";
import { SectionShell } from "@/components/layout-contract";

type Brand = { name: string; domain: string };

function getLogoDevUrl(domain: string) {
	const token = process.env.NEXT_PUBLIC_LOGO_DEV_PUBLISHABLE_KEY;
	if (!token) return null;
	const params = new URLSearchParams({
		token,
		size: "80",
		format: "png",
		retina: "true",
		fallback: "monogram",
	});
	return `https://img.logo.dev/${domain}?${params}`;
}

const columns: {
	logos: Brand[];
	duration: number;
	reverse: boolean;
}[] = [
	{
		logos: [
			{ name: "Stripe", domain: "stripe.com" },
			{ name: "Notion", domain: "notion.so" },
			{ name: "Figma", domain: "figma.com" },
			{ name: "Slack", domain: "slack.com" },
		],
		duration: 22,
		reverse: false,
	},
	{
		logos: [
			{ name: "Vercel", domain: "vercel.com" },
			{ name: "Linear", domain: "linear.app" },
			{ name: "GitHub", domain: "github.com" },
			{ name: "Shopify", domain: "shopify.com" },
		],
		duration: 26,
		reverse: true,
	},
	{
		logos: [
			{ name: "HubSpot", domain: "hubspot.com" },
			{ name: "Airtable", domain: "airtable.com" },
			{ name: "Intercom", domain: "intercom.com" },
			{ name: "Zendesk", domain: "zendesk.com" },
		],
		duration: 20,
		reverse: false,
	},
];

function LogoMark({ brand }: { brand: Brand }) {
	const src = getLogoDevUrl(brand.domain);

	return (
		<div className="flex h-8 items-center gap-2 md:h-9">
			{src ? (
				<img
					alt=""
					aria-hidden
					className="size-6 shrink-0 object-contain opacity-70 grayscale transition-[opacity,filter] duration-300 group-hover:opacity-100 group-hover:grayscale-0 md:size-[1.625rem] outline outline-1 -outline-offset-1 outline-black/10 dark:outline-white/10"
					height={26}
					loading="lazy"
					referrerPolicy="origin"
					src={src}
				/>
			) : null}
			<BrandWordmark
				className={cn(
					"text-[0.8125rem] leading-none text-foreground/70 transition-colors duration-300 group-hover:text-foreground/90 md:text-sm",
					!src && "opacity-80",
				)}
				domain={brand.domain}
				name={brand.name}
			/>
		</div>
	);
}

function LogoColumn({
	logos,
	duration,
	reverse,
	reduceMotion,
	paused,
}: {
	logos: Brand[];
	duration: number;
	reverse: boolean;
	reduceMotion: boolean;
	paused: boolean;
}) {
	const items = [...logos, ...logos];

	return (
		<div className="relative h-56 overflow-hidden md:h-64">
			<motion.div
				animate={
					reduceMotion || paused
						? undefined
						: { y: reverse ? ["-50%", "0%"] : ["0%", "-50%"] }
				}
				className="flex flex-col items-center gap-10 py-2"
				transition={
					reduceMotion || paused
						? undefined
						: {
								duration,
								repeat: Number.POSITIVE_INFINITY,
								ease: "linear",
							}
				}
			>
				{items.map((brand, index) => (
					<div
						className="group flex h-9 shrink-0 items-center justify-center md:h-10"
						key={`${brand.domain}-${index}`}
					>
						<LogoMark brand={brand} />
					</div>
				))}
			</motion.div>
		</div>
	);
}

export function LogoCloudVerticalSection() {
	const reduceMotion = useReducedMotion();
	const [paused, setPaused] = useState(false);

	return (
		<SectionShell spacingMode="compact" className="overflow-hidden bg-background">
			<BrandFonts id="logo-cloud-5-brand-fonts" />
			<div
				className="mx-auto w-full max-w-5xl space-y-8 md:space-y-9"
				onMouseEnter={() => setPaused(true)}
				onMouseLeave={() => setPaused(false)}
			>
				<p className="text-pretty text-center text-muted-foreground text-xs uppercase tracking-[0.2em]">
					Trusted worldwide
				</p>

				<div className="relative mask-[linear-gradient(to_bottom,transparent,black_14%,black_86%,transparent)]">
					<div className="grid grid-cols-1 gap-6 min-[30rem]:grid-cols-3 md:gap-10">
						{columns.map((column) => (
							<LogoColumn
								duration={column.duration}
								key={column.logos[0]?.domain}
								logos={column.logos}
								paused={paused}
								reduceMotion={!!reduceMotion}
								reverse={column.reverse}
							/>
						))}
					</div>
				</div>
			</div>
		</SectionShell>
	);
}
```
