# Logo cloud 14

> Dual-row logo drift with real marks — opposite directions, pause on hover.

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

- Page, with a live preview: https://shadcn.exchange/r/nusaiba/logo-cloud-14
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/nusaiba/logo-cloud-14-light-df24b85553ac68d2.png
- Registry item: https://nusaiba.dev/r/base-nova/logo-cloud-14.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-14.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/14/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 { defaultSpring, motionViewport } from "@/lib/motion-tokens";
import { cn } from "@/lib/utils";
import { SectionShell } from "@/components/layout-contract";

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

const rowTop: Brand[] = [
	{ 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: "Shopify", domain: "shopify.com" },
];

const rowBottom: Brand[] = [
	{ name: "Slack", domain: "slack.com" },
	{ name: "GitHub", domain: "github.com" },
	{ name: "Supabase", domain: "supabase.com" },
	{ name: "OpenAI", domain: "openai.com" },
	{ name: "Turso", domain: "turso.tech" },
	{ name: "HubSpot", domain: "hubspot.com" },
];

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]";

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}`;
}

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

	return (
		<div className="flex h-7 shrink-0 items-center gap-2 md:h-8">
			{src ? (
				<img
					alt=""
					aria-hidden
					className={logoIconClass}
					height={26}
					loading="lazy"
					referrerPolicy="origin"
					src={src}
				/>
			) : null}
			<BrandWordmark
				className={cn(
					"text-[0.9375rem] leading-none text-foreground/85 md:text-base",
					!src && "opacity-80",
				)}
				domain={brand.domain}
				name={brand.name}
			/>
		</div>
	);
}

function DriftRow({
	brands,
	direction,
	paused,
	reduceMotion,
}: {
	brands: Brand[];
	direction: "left" | "right";
	paused: boolean;
	reduceMotion: boolean;
}) {
	const items = [...brands, ...brands];
	const animateX = direction === "left" ? ["0%", "-50%"] : ["-50%", "0%"];

	return (
		<div className="overflow-hidden mask-[linear-gradient(to_right,transparent,black_10%,black_90%,transparent)]">
			<motion.div
				animate={reduceMotion || paused ? undefined : { x: animateX }}
				className="flex w-max gap-8 pr-8 md:gap-10 md:pr-10"
				style={{ skewX: reduceMotion ? 0 : direction === "left" ? -1.25 : 1.25 }}
				transition={
					reduceMotion || paused
						? undefined
						: {
								duration: 32,
								repeat: Number.POSITIVE_INFINITY,
								ease: "linear",
							}
				}
			>
				{items.map((brand, index) => (
					<LogoMark brand={brand} key={`${brand.domain}-${index}`} />
				))}
			</motion.div>
		</div>
	);
}

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

	return (
		<SectionShell spacingMode="compact" className="bg-background" onMouseEnter={() => setPaused(true)}
			onMouseLeave={() => setPaused(false)}
		>
			<BrandFonts id="logo-cloud-14-brand-fonts" />
			<div className="mx-auto flex w-full max-w-5xl flex-col items-center gap-8 md:gap-9">
				<motion.p
					className="text-pretty text-center text-muted-foreground text-sm lowercase tracking-[0.14em]"
					initial={reduceMotion ? false : { opacity: 0, y: 8, filter: "blur(4px)" }}
					transition={defaultSpring}
					viewport={motionViewport}
					whileInView={reduceMotion ? undefined : { opacity: 1, y: 0, filter: "blur(0px)" }}
				>
					dual ribbons in motion
				</motion.p>

				<div className="flex w-full flex-col gap-5 md:gap-6">
					<DriftRow
						brands={rowTop}
						direction="left"
						paused={paused}
						reduceMotion={!!reduceMotion}
					/>
					<DriftRow
						brands={rowBottom}
						direction="right"
						paused={paused}
						reduceMotion={!!reduceMotion}
					/>
				</div>
			</div>
		</SectionShell>
	);
}
```
