# Logo cloud 3

> Logo grid with auto-cycle pulse focus — active mark breathes, neighbors dim on hover.

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

- Page, with a live preview: https://shadcn.exchange/r/nusaiba/logo-cloud-3
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/nusaiba/logo-cloud-3-light-862a4bdb8611dac9.png
- Registry item: https://nusaiba.dev/r/base-nova/logo-cloud-3.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-3.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/3/logo-cloud-section.tsx`

```tsx
"use client";

import { useCallback, useEffect, useState } from "react";
import { motion, useInView, useReducedMotion } from "motion/react";
import { BrandFonts, BrandWordmark } from "@/lib/brand-typography";
import { defaultSpring, motionStagger, motionViewport } from "@/lib/motion-tokens";
import { cn } from "@/lib/utils";
import { SectionShell, ContentRail } from "@/components/layout-contract";

const CYCLE_MS = 3200;

const logoIconClass =
	"size-6 shrink-0 object-contain outline outline-1 -outline-offset-1 outline-black/10 dark:outline-white/10 md:size-[1.625rem]";
const logoWordmarkClass = "text-[0.8125rem] leading-none sm:text-[0.9375rem] md:text-base";

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

const logos = [
	{ name: "Stripe", domain: "stripe.com" },
	{ name: "Notion", domain: "notion.so" },
	{ name: "Vercel", domain: "vercel.com" },
	{ name: "Linear", domain: "linear.app" },
	{ name: "Figma", domain: "figma.com" },
	{ name: "Slack", domain: "slack.com" },
	{ name: "GitHub", domain: "github.com" },
	{ name: "Shopify", domain: "shopify.com" },
];

function PulseCell({
	brand,
	isActive,
	hasFocus,
	onActivate,
	onDeactivate,
	reduceMotion,
	cellVariants,
}: {
	brand: (typeof logos)[number];
	isActive: boolean;
	hasFocus: boolean;
	onActivate: () => void;
	onDeactivate: () => void;
	reduceMotion: boolean;
	cellVariants: {
		hidden: { opacity: number; scale?: number; filter?: string };
		visible: { opacity: number; scale?: number; filter?: string };
	};
}) {
	const src = getLogoDevUrl(brand.domain);
	const dimmed = hasFocus ? !isActive : false;
	const pulsing = isActive && !hasFocus && !reduceMotion;

	return (
		<motion.button
			aria-label={brand.name}
			aria-pressed={isActive}
			className={cn(
				"group relative flex min-h-[5.25rem] w-full items-center justify-center rounded-[var(--radius-md)] border border-border bg-background transition-[background-color,border-color,transform] active:scale-[0.96] sm:min-h-[5.75rem]",
				"after:absolute after:top-1/2 after:left-1/2 after:size-11 after:-translate-1/2",
				isActive && "border-foreground/10 bg-muted/15",
			)}
			onBlur={onDeactivate}
			onFocus={onActivate}
			onMouseEnter={onActivate}
			onMouseLeave={onDeactivate}
			type="button"
			variants={cellVariants}
		>
			<motion.div
				animate={
					reduceMotion
						? undefined
						: dimmed
							? { opacity: 0.42, scale: 0.97, filter: "blur(1px)" }
							: pulsing
								? { opacity: 1, scale: [1, 1.03, 1], filter: "blur(0px)" }
								: { opacity: isActive ? 1 : 0.55, scale: 1, filter: "blur(0px)" }
				}
				className="flex h-7 items-center gap-2 px-2 sm:gap-2.5 md:h-8"
				transition={
					pulsing
						? { duration: 2.4, repeat: Number.POSITIVE_INFINITY, ease: "easeInOut" }
						: { type: "spring", duration: 0.32, bounce: 0 }
				}
			>
				{src ? (
					<img
						alt=""
						aria-hidden
						className={logoIconClass}
						height={26}
						loading="lazy"
						referrerPolicy="origin"
						src={src}
					/>
				) : (
					<span className="font-medium text-foreground/45 text-xs">{brand.name}</span>
				)}
				<BrandWordmark
					className={cn(
						logoWordmarkClass,
						"hidden text-foreground/80 sm:block",
						isActive && "text-foreground",
					)}
					domain={brand.domain}
					name={brand.name}
				/>
			</motion.div>
		</motion.button>
	);
}

export function LogoCloudPulseSection() {
	const reduceMotion = useReducedMotion();
	const [rootNode, setRootNode] = useState<HTMLElement | null>(null);
	const isInView = useInView(
		{ current: rootNode },
		{ ...motionViewport, amount: 0.35 },
	);
	const [active, setActive] = useState(0);
	const [cycleActive, setCycleActive] = useState(false);
	const [hoverFocus, setHoverFocus] = useState<string | null>(null);

	useEffect(() => {
		if (!isInView || reduceMotion) return;
		setCycleActive(true);
	}, [isInView, reduceMotion]);

	useEffect(() => {
		if (!cycleActive || reduceMotion || hoverFocus) return;
		const timer = setInterval(() => {
			setActive((current) => (current + 1) % logos.length);
		}, CYCLE_MS);
		return () => clearInterval(timer);
	}, [cycleActive, hoverFocus, reduceMotion]);

	const selectBrand = useCallback((domain: string, index: number) => {
		setHoverFocus(domain);
		setActive(index);
		setCycleActive(false);
	}, []);

	const clearFocus = useCallback((domain: string) => {
		setHoverFocus((current) => (current === domain ? null : current));
		setCycleActive(true);
	}, []);

	const cellVariants = reduceMotion
		? { hidden: { opacity: 1 }, visible: { opacity: 1 } }
		: {
				hidden: { opacity: 0, scale: 0.97, filter: "blur(4px)" },
				visible: { opacity: 1, scale: 1, filter: "blur(0px)" },
			};

	return (
		<SectionShell spacingMode="compact" className="bg-background" ref={setRootNode}>
			<BrandFonts id="logo-cloud-3-brand-fonts" />
			<ContentRail maxWidth="max-w-4xl" className="space-y-8 md:space-y-9">
				<div className="mx-auto max-w-sm space-y-2 text-center">
					<motion.p
						className="font-medium text-primary text-xs uppercase tracking-[0.22em]"
						initial={reduceMotion ? false : { opacity: 0, y: 8, filter: "blur(4px)" }}
						transition={defaultSpring}
						viewport={motionViewport}
						whileInView={reduceMotion ? undefined : { opacity: 1, y: 0, filter: "blur(0px)" }}
					>
						Pulse grid
					</motion.p>
					<motion.p
						className="text-balance font-medium text-lg tracking-tight md:text-xl"
						initial={reduceMotion ? false : { opacity: 0, y: 8, filter: "blur(4px)" }}
						transition={{ ...defaultSpring, delay: motionStagger.item }}
						viewport={motionViewport}
						whileInView={reduceMotion ? undefined : { opacity: 1, y: 0, filter: "blur(0px)" }}
					>
						In steady rotation
					</motion.p>
					<motion.p
						className="text-pretty text-muted-foreground text-sm leading-relaxed"
						initial={reduceMotion ? false : { opacity: 0, y: 8, filter: "blur(4px)" }}
						transition={{ ...defaultSpring, delay: motionStagger.item * 2 }}
						viewport={motionViewport}
						whileInView={reduceMotion ? undefined : { opacity: 1, y: 0, filter: "blur(0px)" }}
					>
						Hover a cell or wait for the cycle to advance.
					</motion.p>
				</div>

				<motion.div
					animate={isInView ? "visible" : "hidden"}
					className="grid grid-cols-2 gap-3 sm:grid-cols-4 md:gap-4"
					initial="hidden"
					onMouseLeave={() => setHoverFocus(null)}
					variants={{
						hidden: {},
						visible: {
							transition: {
								staggerChildren: motionStagger.item,
								delayChildren: 0.04,
							},
						},
					}}
				>
					{logos.map((brand, index) => (
						<PulseCell
							brand={brand}
							cellVariants={cellVariants}
							hasFocus={hoverFocus !== null}
							isActive={active === index}
							key={brand.domain}
							onActivate={() => selectBrand(brand.domain, index)}
							onDeactivate={() => clearFocus(brand.domain)}
							reduceMotion={!!reduceMotion}
						/>
					))}
				</motion.div>
			</ContentRail>
		</SectionShell>
	);
}
```
