A closing call to action on a dark band, in both color schemes. The apps that top a dozen store charts slide around the headline and email field like a sliding puzzle; point at one for its name and its chart.
registry/blocks/slide-puzzle-cta.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 closing ask yours: the
* copy, the sign-up field, and the apps on the tiles (each a
* name, the chart it tops and two colors for its icon). The
* band is dark in both color schemes.
* ─────────────────────────────────────────────────────────── */
/** The one accent: the button and the focus rings. */
const ACCENT = "#ff5a1f";
const BADGE = "14 days free";
const HEADLINE = "Your app belongs on the top row.";
const SUBLINE = "Connect a store account and read your first ranking report in under five minutes.";
const FIELD = { label: "Work email", placeholder: "you@studio.com", button: "Start free" };
const FOOTNOTE = "No card needed. Cancel in two clicks.";
type App = { id: string; name: string; chart: string; from: string; to: string; glyph: React.ReactNode };
const APPS: App[] = [
{ id: "fieldnote", name: "Fieldnote", chart: "Productivity", from: "#ff8a3d", to: "#ff3d00", glyph: <path d="M7 4.5h10a1.5 1.5 0 0 1 1.5 1.5v12A1.5 1.5 0 0 1 17 19.5H7A1.5 1.5 0 0 1 5.5 18V6A1.5 1.5 0 0 1 7 4.5ZM8.5 9.5h7M8.5 13h4.5" /> },
{ id: "tidewatch", name: "Tidewatch", chart: "Weather", from: "#38bdf8", to: "#0369a1", glyph: <path d="M4 14c2-2 4-2 6 0s4 2 6 0 3-1.5 4-1M4 18c2-2 4-2 6 0s4 2 6 0 3-1.5 4-1M12 4.5v5M8.5 7.5l3.5-3 3.5 3" /> },
{ id: "strider", name: "Strider", chart: "Health & Fitness", from: "#4ade80", to: "#15803d", glyph: <path d="M13.5 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM8 21l3-6 3 2.5V21M6 11l3.5-3.5L13 9l2.5 3H19M11 15l-1.5-4" /> },
{ id: "penny", name: "Penny Jar", chart: "Finance", from: "#facc15", to: "#ca8a04", glyph: <path d="M12 20a7.5 7.5 0 1 0 0-15 7.5 7.5 0 0 0 0 15ZM14.5 9.5c-.5-1-1.4-1.5-2.5-1.5-1.5 0-2.5.8-2.5 2 0 2.8 5 1.4 5 4 0 1.2-1 2-2.5 2-1.2 0-2.1-.5-2.6-1.5M12 6.5V8M12 16v1.5" /> },
{ id: "quayside", name: "Quayside", chart: "Travel", from: "#818cf8", to: "#4338ca", glyph: <path d="M3.5 16.5 12 4l8.5 12.5M7 16.5h10M12 4v15.5M4 19.5h16" /> },
{ id: "lumen", name: "Lumen Focus", chart: "Education", from: "#c084fc", to: "#7e22ce", glyph: <path d="M12 20.5a7.5 7.5 0 1 0 0-15 7.5 7.5 0 0 0 0 15ZM12 9v4l2.5 2M10 2.5h4" /> },
{ id: "crumb", name: "Crumb", chart: "Food & Drink", from: "#fb7185", to: "#be123c", glyph: <path d="M5 11h14l-1.2 7.2A2 2 0 0 1 15.8 20H8.2a2 2 0 0 1-2-1.8zM8 11c0-3 1.8-5.5 4-5.5S16 8 16 11M12 5.5V3.5" /> },
{ id: "loop", name: "Loopback", chart: "Music", from: "#2dd4bf", to: "#0f766e", glyph: <path d="M9 18.5V6.5l10-2v12M9 18.5a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0ZM19 16.5a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z" /> },
{ id: "sketchy", name: "Sketchbook", chart: "Graphics & Design", from: "#f472b6", to: "#be185d", glyph: <path d="m14.5 4.5 5 5L9 20H4v-5zM12.5 6.5l5 5" /> },
{ id: "parcel", name: "Parcel Pigeon", chart: "Business", from: "#a3e635", to: "#4d7c0f", glyph: <path d="M4 8.5 12 4.5l8 4v8l-8 4-8-4zM4 8.5l8 4 8-4M12 12.5v8" /> },
{ id: "hearth", name: "Hearth", chart: "Lifestyle", from: "#fdba74", to: "#ea580c", glyph: <path d="M12 20.5s-7.5-4.6-7.5-10A4.5 4.5 0 0 1 12 7.2a4.5 4.5 0 0 1 7.5 3.3c0 5.4-7.5 10-7.5 10Z" /> },
{ id: "nightjar", name: "Nightjar", chart: "Sleep", from: "#6366f1", to: "#1e1b4b", glyph: <path d="M19.5 14.5A7.5 7.5 0 0 1 9.5 4.5a7.5 7.5 0 1 0 10 10Z" /> },
];
const TILES: SlidePuzzleTile[] = APPS.map((app) => ({
id: app.id,
label: `${app.name} · #1 in ${app.chart}`,
href: `#${app.id}`,
icon: (
<span
className="grid size-full place-items-center text-white shadow-[inset_0_0_0_1px_rgb(255_255_255/0.16),inset_0_1px_0_rgb(255_255_255/0.25)]"
style={{ backgroundImage: `linear-gradient(150deg, ${app.from}, ${app.to})` }}
>
<svg viewBox="0 0 24 24" className="size-[45%]" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
{app.glyph}
</svg>
</span>
),
}));
/* ── Section ─────────────────────────────────────────────── */
export function SlidePuzzleCta() {
const [sent, setSent] = React.useState(false);
return (
<section style={{ "--accent": ACCENT } as React.CSSProperties} className="bg-[#f5f5f6] px-3 py-3 sm:px-4 dark:bg-[#09090b]">
<div className="mx-auto max-w-6xl overflow-hidden rounded-[28px] bg-[#0b0b0d] text-white shadow-[0_0_0_1px_rgb(255_255_255/0.06)]">
<SlidePuzzle
tiles={TILES}
cell={72}
seed={11}
className="text-white [--slide-puzzle-ring:var(--accent)] [--slide-puzzle-tile:#18181b] dark:[--slide-puzzle-tile:#18181b] [&_[data-slide-puzzle-label]]:bg-white [&_[data-slide-puzzle-label]]:text-zinc-950"
>
<div className="flex max-w-lg flex-col items-center text-center">
<p className="rounded-full bg-white/[0.07] px-3 py-1 text-xs font-medium text-zinc-300 shadow-[0_0_0_1px_rgb(255_255_255/0.1)]">{BADGE}</p>
<h2 className="mt-5 text-[2rem] leading-[1.06] font-semibold tracking-[-0.04em] text-balance sm:text-[3.25rem]">{HEADLINE}</h2>
<p className="mt-4 max-w-sm text-[15px] leading-relaxed text-pretty text-zinc-400">{SUBLINE}</p>
<form
onSubmit={(e) => {
e.preventDefault();
setSent(true);
}}
className="mt-8 flex w-full max-w-sm items-center gap-2 rounded-full bg-white/[0.06] p-1.5 pl-4 ring-1 ring-white/12 focus-within:ring-2 focus-within:ring-(--accent)"
>
<label htmlFor="rung-cta-email" className="sr-only">
{FIELD.label}
</label>
<input
id="rung-cta-email"
type="email"
required
autoComplete="email"
placeholder={FIELD.placeholder}
className="h-10 min-w-0 flex-1 bg-transparent text-[15px] text-white outline-none placeholder:text-zinc-500"
/>
<button
type="submit"
className="flex h-10 shrink-0 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-white active:scale-[0.97]"
>
{FIELD.button}
<svg viewBox="0 0 16 16" className="size-3.5" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="m6 4 4 4-4 4" />
</svg>
</button>
</form>
<p className="mt-4 text-[13px] text-zinc-500" aria-live="polite">
{sent ? "Check your inbox for the link." : FOOTNOTE}
</p>
</div>
</SlidePuzzle>
</div>
</section>
);
}
Live preview and the components it's built with: https://keyframeui.com/blocks/cta-sections/slide-puzzle-cta
