A logo cloud that tells you why each logo is there. The customers' marks sit quiet in a dock; pick one, or let it walk through them, and its mark takes its color while a small card launches out of it with the one number its story is about and a link to the rest.
registry/blocks/launch-dock-logos.tsx
"use client";
import * as React from "react";
import { LaunchDock, type LaunchDockItem } from "@/components/ui/launch-dock";
/* ── Content ─────────────────────────────────────────────────
* Everything you'd change to make the section yours: the line
* over the logos, and each customer's mark, color and the one
* number their story is about. The marks are small SVGs; swap
* in your customers' logos.
* ─────────────────────────────────────────────────────────── */
const LABEL = "Teams that run on Shoal";
const HINT = "Pick a logo to see what changed.";
const STORY_LINK = "Read the story";
type Customer = {
id: string;
name: string;
/** The mark's color once its story is open. */
color: string;
/** The mark: SVG shapes on a 24-unit grid, drawn in the current color. */
mark: React.ReactNode;
metric: string;
metricLabel: string;
story: string;
href: string;
};
const CUSTOMERS: Customer[] = [
{
id: "northwind",
name: "Northwind Air",
color: "#2563eb",
mark: <path d="M3 15.5 21 6l-6.5 13-2.2-5.3Z" />,
metric: "6 sec",
metricLabel: "from a gate tablet to the manifest",
story: "2,400 crew tablets check passengers in at the gate, online or not, and every one lands in the manifest.",
href: "#northwind",
},
{
id: "harbor",
name: "Harbor",
color: "#0d9488",
mark: (
<>
<circle cx="12" cy="6.5" r="2.5" />
<path d="M11 9h2v8.2a5.5 5.5 0 0 0 4.6-3.7l-1.7.4L18.5 11l1.3 4.6-1.6-.6A7.5 7.5 0 0 1 12 20a7.5 7.5 0 0 1-6.2-5l-1.6.6L5.5 11l2.6 2.9-1.7-.4A5.5 5.5 0 0 0 11 17.2Z" />
</>
),
metric: "11 hrs",
metricLabel: "back per inspector, every week",
story: "Field inspections write to the phone first and sync when the truck finds signal. No more evening data entry.",
href: "#harbor",
},
{
id: "kvarter",
name: "Kvarter",
color: "#ea580c",
mark: (
<>
<rect x="4" y="4" width="7" height="7" rx="1" />
<rect x="13" y="4" width="7" height="7" rx="3.5" />
<rect x="4" y="13" width="7" height="7" rx="3.5" />
<rect x="13" y="13" width="7" height="7" rx="1" />
</>
),
metric: "−92%",
metricLabel: "merge conflicts on shared plans",
story: "Thirty architects edit one floor plan at a time of day that used to mean overwriting each other.",
href: "#kvarter",
},
{
id: "okto",
name: "Okto",
color: "#7c3aed",
mark: <path d="M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18Zm0 4.5a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9Z" fillRule="evenodd" />,
metric: "40 ms",
metricLabel: "median note sync, phone to laptop",
story: "Meeting notes appear on every attendee's device before the next agenda item starts.",
href: "#okto",
},
{
id: "ferngrove",
name: "Ferngrove Schools",
color: "#16a34a",
mark: <path d="M12 2 4.5 14h4L6 19h12l-2.5-5h4Z" />,
metric: "180",
metricLabel: "schools on one timetable",
story: "A room change made in the office reaches every teacher's tablet before the bell, even in the basement labs.",
href: "#ferngrove",
},
{
id: "tinta",
name: "Tinta Print",
color: "#db2777",
mark: (
<>
<path d="M12 3c3 4 6 7 6 10.5a6 6 0 0 1-12 0C6 10 9 7 12 3Z" />
</>
),
metric: "3×",
metricLabel: "orders through the shop floor",
story: "Press operators mark jobs done on a tablet by the machine, and the front desk sees it in the same second.",
href: "#tinta",
},
];
/* ── Section ─────────────────────────────────────────────── */
export function LaunchDockLogos() {
const [open, setOpen] = React.useState(CUSTOMERS[0].id);
// The open customer's mark takes its color; the rest stay quiet, like a logo wall.
const items: LaunchDockItem[] = CUSTOMERS.map((customer) => ({
id: customer.id,
label: customer.name,
icon: <Mark customer={customer} lit={customer.id === open} />,
content: <Story customer={customer} />,
}));
return (
<section className="bg-white text-zinc-950 dark:bg-zinc-950 dark:text-white">
<div className="mx-auto max-w-6xl px-6 py-20 sm:py-24">
<div className="text-center">
<h2 className="text-sm font-semibold tracking-tight">{LABEL}</h2>
<p className="mt-1 text-sm text-zinc-500 dark:text-zinc-400">{HINT}</p>
</div>
<LaunchDock
items={items}
value={open}
onValueChange={(id) => setOpen(id)}
frame="card"
fade={false}
autoplay={4500}
windowSize={[560, 236]}
dockLabel="Customers"
className="mx-auto mt-8 max-w-[560px]"
trayClassName="bg-zinc-50/80 shadow-none ring-zinc-950/[0.06] dark:bg-white/[0.04] dark:shadow-none"
windowClassName="bg-zinc-50 ring-zinc-950/[0.06] dark:bg-zinc-900"
/>
</div>
</section>
);
}
function Mark({ customer, lit }: { customer: Customer; lit: boolean }) {
return (
<span className="grid size-full place-items-center bg-white dark:bg-zinc-900">
<svg
viewBox="0 0 24 24"
aria-hidden
style={{ color: lit ? customer.color : undefined }}
className="size-[56%] fill-current text-zinc-400 transition-colors duration-300 motion-reduce:transition-none dark:text-zinc-500"
>
{customer.mark}
</svg>
</span>
);
}
function Story({ customer }: { customer: Customer }) {
return (
<div className="flex h-full flex-col justify-between px-9 py-8">
<div className="flex items-center justify-between">
<p className="text-[13px] font-semibold tracking-[0.08em] text-zinc-500 uppercase dark:text-zinc-400">{customer.name}</p>
<a
href={customer.href}
className="inline-flex items-center gap-1 rounded-sm text-[14px] font-semibold text-zinc-950 underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-950 dark:text-white dark:focus-visible:outline-white"
>
{STORY_LINK}
<svg viewBox="0 0 16 16" className="size-4" fill="none" stroke="currentColor" strokeWidth={1.6} aria-hidden>
<path d="M3.5 8h9m-3.5-3.5L12.5 8 9 11.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</a>
</div>
<p className="flex items-baseline gap-3">
<span style={{ color: customer.color }} className="text-[46px] leading-none font-semibold tracking-[-0.04em] tabular-nums">
{customer.metric}
</span>
<span className="text-[16px] text-zinc-600 dark:text-zinc-400">{customer.metricLabel}</span>
</p>
<p className="text-[16.5px] leading-relaxed text-pretty text-zinc-800 dark:text-zinc-200">{customer.story}</p>
</div>
);
}
Live preview and the components it's built with: https://keyframeui.com/blocks/logo-clouds/launch-dock-logos