An integrations section where fourteen tool tiles sit in a grid of slots around the headline. They start scrambled and solve themselves like a sliding puzzle, then one slides over every few seconds; point at a tile for its name, or sweep the mouse fast to shove a row along. Under the card, three notes on how the integrations work.
registry/blocks/slide-puzzle-integrations.tsx
"use client";
import * as React from "react";
import { SlidePuzzle, type SlidePuzzleTile } from "@/components/ui/slide-puzzle";
/* ── Content ─────────────────────────────────────────────────
* Everything you'd change to make the section yours: the copy,
* the integrations on the tiles (a name, what it is, a link and
* a mark) and the three notes under the card. Every name and
* mark here is made up; swap in your own logos as `icon`.
* ─────────────────────────────────────────────────────────── */
/** The one accent: the primary button and the focus rings. */
const ACCENT = "#ff5a1f";
const BADGE = "Integrations";
const HEADLINE = "Plugs into everything that ships your app.";
const SUBLINE =
"Rung reads your store accounts, reviews and analytics, and posts what moved to wherever your team already talks.";
const PRIMARY_CTA = { label: "Browse 38 integrations", href: "#integrations" };
const SECONDARY_CTA = { label: "Request one", href: "#request" };
const NOTES = [
{ title: "Two-way listing sync", body: "Accept a suggested subtitle in Rung and it lands in your store listing, ready for review." },
{ title: "Alerts where you talk", body: "Move up or slip down for a keyword you care about and your team channel hears first." },
{ title: "Webhooks for the rest", body: "Every rank change as a signed event, so your own dashboards stay current." },
];
function Mark({ children }: { children: React.ReactNode }) {
return (
<svg viewBox="0 0 24 24" className="size-6" aria-hidden>
{children}
</svg>
);
}
const TILES: SlidePuzzleTile[] = [
{
id: "storefront",
label: "Storefront · App store",
href: "#storefront",
icon: (
<Mark>
<path d="M5.5 8h13l-1 11.5a1.5 1.5 0 0 1-1.5 1.4H8a1.5 1.5 0 0 1-1.5-1.4z" fill="#2563eb" />
<path d="M9 9.5V7a3 3 0 0 1 6 0v2.5" fill="none" stroke="#60a5fa" strokeWidth="1.8" strokeLinecap="round" />
</Mark>
),
},
{
id: "playfield",
label: "Playfield · Android store",
href: "#playfield",
icon: (
<Mark>
<path d="M6 4.2v15.6c0 .8.9 1.3 1.6.9l13-7.8a1 1 0 0 0 0-1.8l-13-7.8C6.9 2.9 6 3.4 6 4.2Z" fill="#10b981" />
<path d="M6 4.2 14.2 12 6 19.8" fill="none" stroke="#a7f3d0" strokeWidth="1.4" strokeLinejoin="round" />
</Mark>
),
},
{
id: "chatter",
label: "Chatter · Team chat",
href: "#chatter",
icon: (
<Mark>
<path d="M4 7a4 4 0 0 1 4-4h8a4 4 0 0 1 4 4v5.5a4 4 0 0 1-4 4h-5L6.5 20.5v-4.2A4 4 0 0 1 4 12.5z" fill="#7c3aed" />
<path d="M8.5 8.5h7M8.5 11.5h4.5" stroke="#ddd6fe" strokeWidth="1.7" strokeLinecap="round" />
</Mark>
),
},
{
id: "tracklight",
label: "Tracklight · Analytics",
href: "#tracklight",
icon: (
<Mark>
<rect x="4" y="12" width="4" height="8" rx="1.3" fill="#fdba74" />
<rect x="10" y="7.5" width="4" height="12.5" rx="1.3" fill="#fb923c" />
<rect x="16" y="3.5" width="4" height="16.5" rx="1.3" fill="#ff5a1f" />
</Mark>
),
},
{
id: "paywall-kit",
label: "Paywall Kit · Subscriptions",
href: "#paywall-kit",
icon: (
<Mark>
<rect x="2.5" y="5" width="19" height="14" rx="3" fill="#4f46e5" />
<rect x="2.5" y="8.4" width="19" height="2.6" fill="#312e81" />
<rect x="5.2" y="14.2" width="5.5" height="2" rx="1" fill="#c7d2fe" />
</Mark>
),
},
{
id: "bugnet",
label: "Bugnet · Crash reports",
href: "#bugnet",
icon: (
<Mark>
<ellipse cx="12" cy="13.5" rx="5.5" ry="6.5" fill="#e11d48" />
<path d="M12 7v13M7 11H3.5M20.5 11H17M7 16H4M20 16h-3M9 5l1.5 2M15 5l-1.5 2" stroke="#e11d48" strokeWidth="1.6" strokeLinecap="round" />
<path d="M12 8v11.5" stroke="#fecdd3" strokeWidth="1.3" />
</Mark>
),
},
{
id: "lintel",
label: "Lintel · Issues",
href: "#lintel",
icon: (
<Mark>
<circle cx="12" cy="12" r="8.5" fill="#0ea5e9" />
<path d="m8.2 12.2 2.6 2.6 5-5.4" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</Mark>
),
},
{
id: "sheetwise",
label: "Sheetwise · Spreadsheets",
href: "#sheetwise",
icon: (
<Mark>
<rect x="4" y="3.5" width="16" height="17" rx="2.5" fill="#16a34a" />
<path d="M4 9.5h16M4 15h16M10 9.5v11" stroke="#bbf7d0" strokeWidth="1.4" />
</Mark>
),
},
{
id: "folio",
label: "Folio · Docs",
href: "#folio",
icon: (
<Mark>
<path d="M6.5 3h7.5l4.5 4.5V19a2 2 0 0 1-2 2h-10a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z" fill="#f59e0b" />
<path d="M14 3v4.5h4.5" fill="#fde68a" />
<path d="M8 12h8M8 15.5h5.5" stroke="#fff" strokeWidth="1.6" strokeLinecap="round" />
</Mark>
),
},
{
id: "pixelboard",
label: "Pixelboard · Design files",
href: "#pixelboard",
icon: (
<Mark>
<circle cx="8" cy="7" r="3.5" fill="#f472b6" />
<circle cx="16" cy="7" r="3.5" fill="#db2777" />
<circle cx="8" cy="15" r="3.5" fill="#fb923c" />
<rect x="12.5" y="11.5" width="7" height="7" rx="2" fill="#a855f7" />
</Mark>
),
},
{
id: "hookline",
label: "Hookline · Webhooks",
href: "#hookline",
icon: (
<Mark>
<path d="M9.5 6.5a3.5 3.5 0 1 1 5.6 2.8L12 14.6" fill="none" stroke="#06b6d4" strokeWidth="2.2" strokeLinecap="round" />
<path d="M7.2 19.2a3.5 3.5 0 0 1 .2-6.9H14" fill="none" stroke="#0891b2" strokeWidth="2.2" strokeLinecap="round" />
<path d="M16 18.8a3.5 3.5 0 0 0 2.8-5.6l-3-5.2" fill="none" stroke="#22d3ee" strokeWidth="2.2" strokeLinecap="round" />
</Mark>
),
},
{
id: "starboard",
label: "Starboard · Reviews",
href: "#starboard",
icon: (
<Mark>
<path d="m12 3.2 2.6 5.3 5.8.8-4.2 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8L3.6 9.3l5.8-.8z" fill="#facc15" stroke="#eab308" strokeWidth="1" strokeLinejoin="round" />
</Mark>
),
},
{
id: "atlas",
label: "Atlas · Localization",
href: "#atlas",
icon: (
<Mark>
<circle cx="12" cy="12" r="8.5" fill="#3b82f6" />
<path d="M3.5 12h17M12 3.5c2.4 2.4 3.5 5.2 3.5 8.5s-1.1 6.1-3.5 8.5c-2.4-2.4-3.5-5.2-3.5-8.5S9.6 5.9 12 3.5Z" fill="none" stroke="#bfdbfe" strokeWidth="1.4" />
</Mark>
),
},
{
id: "postbox",
label: "Postbox · Email digest",
href: "#postbox",
icon: (
<Mark>
<rect x="3" y="5" width="18" height="14" rx="3.2" fill="#ff5a1f" />
<path d="M6 8.6l6 4.4 6-4.4" fill="none" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
</Mark>
),
},
];
/* ── Section ─────────────────────────────────────────────── */
export function SlidePuzzleIntegrations() {
return (
<section
style={{ "--accent": ACCENT } as React.CSSProperties}
className="bg-[#f5f5f6] px-3 py-20 text-zinc-950 sm:px-4 sm:py-28 dark:bg-[#09090b] dark:text-white"
>
<div className="mx-auto max-w-6xl overflow-hidden rounded-[28px] bg-white shadow-[0_1px_2px_rgb(0_0_0/0.04),0_0_0_1px_rgb(0_0_0/0.05)] dark:bg-[#111113] dark:shadow-[0_0_0_1px_rgb(255_255_255/0.07)]">
<SlidePuzzle
tiles={TILES}
cell={68}
className="[--slide-puzzle-ring:var(--accent)] [--slide-puzzle-tile:#ffffff] dark:[--slide-puzzle-tile:#1b1b1f]"
>
<div className="flex max-w-md flex-col items-center text-center">
<p className="inline-flex items-center gap-1.5 rounded-full bg-white px-2.5 py-1 text-xs font-medium text-zinc-700 shadow-[0_0_0_1px_rgb(0_0_0/0.08)] dark:bg-white/[0.06] dark:text-zinc-300 dark:shadow-[0_0_0_1px_rgb(255_255_255/0.1)]">
<span className="grid grid-cols-2 gap-[2px]" aria-hidden>
<span className="size-[5px] rounded-[1.5px] bg-(--accent)" />
<span className="size-[5px] rounded-[1.5px] bg-zinc-300 dark:bg-zinc-600" />
<span className="size-[5px] rounded-[1.5px] bg-zinc-300 dark:bg-zinc-600" />
<span className="size-[5px] rounded-[1.5px] bg-zinc-300 dark:bg-zinc-600" />
</span>
{BADGE}
</p>
<h2 className="mt-5 text-[1.9rem] leading-[1.1] font-semibold tracking-[-0.035em] text-balance sm:text-[2.6rem]">{HEADLINE}</h2>
<p className="mt-4 max-w-sm text-[15px] leading-relaxed text-pretty text-zinc-600 dark:text-zinc-400">{SUBLINE}</p>
<div className="mt-7 flex flex-wrap items-center justify-center gap-2.5">
<a
href={PRIMARY_CTA.href}
className="flex h-10 items-center gap-1 rounded-full bg-(--accent) pr-3.5 pl-4 text-sm font-semibold text-white transition-[filter,transform] duration-150 hover:brightness-110 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--accent) active:scale-[0.97]"
>
{PRIMARY_CTA.label}
<Chevron className="size-3.5" />
</a>
<a
href={SECONDARY_CTA.href}
className="flex h-10 items-center rounded-full bg-white px-4 text-sm font-semibold shadow-[0_1px_2px_rgb(0_0_0/0.05),0_0_0_1px_rgb(0_0_0/0.1)] transition-colors duration-150 hover:bg-zinc-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--accent) dark:bg-white/[0.06] dark:shadow-[0_0_0_1px_rgb(255_255_255/0.12)] dark:hover:bg-white/[0.1]"
>
{SECONDARY_CTA.label}
</a>
</div>
</div>
</SlidePuzzle>
<ul className="grid border-t border-zinc-950/[0.07] sm:grid-cols-3 dark:border-white/[0.07]">
{NOTES.map((note, i) => (
<li
key={note.title}
className={
i > 0
? "border-t border-zinc-950/[0.07] px-7 py-6 sm:border-t-0 sm:border-l dark:border-white/[0.07]"
: "px-7 py-6"
}
>
<p className="text-sm font-semibold tracking-[-0.01em]">{note.title}</p>
<p className="mt-1.5 text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">{note.body}</p>
</li>
))}
</ul>
</div>
</section>
);
}
function Chevron({ className }: { className?: string }) {
return (
<svg viewBox="0 0 16 16" className={className} fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="m6 4 4 4-4 4" />
</svg>
);
}
Live preview and the components it's built with: https://keyframeui.com/blocks/integrations/slide-puzzle-integrations