# Files changed · PR \#66 · screen/col

[View on GitCafe](https://git.cafe/screen/col/pull/66/diffs)

Repository: [screen/col](https://git.cafe/screen/col)

Visibility: public

[Scale preview stories to every eligible library and component \(\#64\)](https://git.cafe/screen/col/pull/66.md)

Version: 1

Head commit: 4f749470d7161bf4a1344969d6ca7abfa4a1d149

Base commit: 69fb62d64838418945518001e78e2a230d149fcc

Comparison base: 69fb62d64838418945518001e78e2a230d149fcc

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## app/api/mcp/route.ts

Status: modified; +2; −0

```diff
@@ -4,6 +4,7 @@
 import { libraryDetails } from "@/data/library-details";
 import { libraryCorpus } from "@/data/library-corpus";
 import { compatibility } from "@/data/compatibility";
+import { previewStories } from "@/data/preview-stories";
 import { createColMcpServer } from "@/lib/mcp-server";
 
 /**
@@ -19,6 +20,7 @@
     details: libraryDetails,
     corpus: libraryCorpus,
     compatibility,
+    previewStories,
   }),
 );
 
```

## app/docs/agents/page.tsx

Status: modified; +1; −1

```diff
@@ -13,7 +13,7 @@
       <DocsHeader section="Get started" title="Agents and LLMs" lead="Give your agent Col’s catalog, then verify the shortlist against each library’s official docs." />
 
       <DocsSection title="Connect Col MCP">
-        <DocsText>Connect your coding agent to Col to search libraries and verified components, then inspect their setup and source links. The server is read-only and needs no credentials.</DocsText>
+        <DocsText>Connect your coding agent to Col to search libraries and verified components, then inspect their setup and source links. When Col has recorded a story, <code>get_component</code> also shows how the component looks in a use-case scene — a themed preview URL and a screenshot — so fit can be checked before implementing. The server is read-only and needs no credentials.</DocsText>
         <DocsLinks items={[
           { href: "/mcp", title: "Set up Col MCP", text: "Client commands, configuration, plugins, and troubleshooting." },
           { href: "/mcp/agents", title: "MCP agent guide", text: "Setup prompt, tool arguments, and the component workflow." },
```

## app/docs/find-a-library/page.tsx

Status: modified; +5; −0

```diff
@@ -21,6 +21,11 @@
         <DocsNote>Component coverage is partial and grows by contribution. A component missing from Col is not necessarily missing from the library, and Col never lists a component just because a library’s tags are broad.</DocsNote>
       </DocsSection>
 
+      <DocsSection title="See it in a use case">
+        <DocsText>Library pages render Col’s own component previews: each recorded component shown in a use-case scene, with a shareable preview page you can open to see it in your project’s theme.</DocsText>
+        <DocsNote>A preview renders one recorded story, not the library’s full variant set, and a missing preview is not evidence a component cannot be previewed.</DocsNote>
+      </DocsSection>
+
       <DocsSection title="Filter and compare">
         <DocsText>Narrow results by category, supported stack, and use case. Open a listing for setup steps and links to the project’s official documentation.</DocsText>
       </DocsSection>
```

## app/globals.css

Status: modified; +11; −0

```diff
@@ -57,6 +57,7 @@
 html.light .hero-cta-primary { border-color: var(--col-brand); background: var(--col-brand); color: #fff; }
 
 :root {
+  --radius: 0.5rem;
   --col-brand: #0800ff;
   --col-blue: #69a9ff;
   --background: #000;
@@ -815,6 +816,11 @@
   }
 }
 @theme inline {
+  --radius-sm: calc(var(--radius) - 4px);
+  --radius-md: calc(var(--radius) - 2px);
+  --radius-lg: var(--radius);
+  --radius-xl: calc(var(--radius) + 4px);
+  --radius-2xl: calc(var(--radius) + 8px);
   --color-background: var(--background);
   --color-foreground: var(--foreground);
   --color-card: var(--card);
@@ -2197,6 +2203,11 @@
 .ld-section { margin-top: 36px; }
 .ld-section h2 { margin-bottom: 12px; color: var(--lib-text); font-size: 15px; font-weight: 800; }
 .ld-section-note { margin: -6px 0 12px; color: var(--lib-text-3); font-size: 13px; }
+/* Component preview renders, one per recorded story. */
+.ld-previews { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
+.ld-preview-link { display: grid; gap: 8px; }
+.ld-preview-link img { width: 100%; height: auto; }
+.ld-preview-caption { color: var(--lib-text-3); font-size: 12px; }
 /* Pricing and community-collection notices under the description; only shown when something is paid or third-party. */
 .ld-notices { display: grid; gap: 8px; max-width: 64ch; margin-top: 16px; }
 .ld-notice { display: flex; align-items: flex-start; gap: 10px; margin: 0; padding: 10px 12px; border-radius: 12px; background: var(--lib-chip); color: var(--lib-text-2); font-size: 13.5px; line-height: 1.55; }
```

## app/llms.txt/route.ts

Status: modified; +18; −10

```diff
@@ -3,19 +3,27 @@
 import { publisherOf } from "@/lib/integration-directory";
 import { hostname } from "@/lib/utils";
 import { integrationPath, libraryPath, siteUrl } from "@/lib/site";
+import { previewPath, shippableStories } from "@/lib/preview-stories";
+import { previewStories } from "@/data/preview-stories";
 
 export const dynamic = "force-static";
 
 export function GET() {
-  const entries = libraries.map((library) => [
-    `## ${library.name}`,
-    `- Domain: [${hostname(library.url)}](${library.url})`,
-    `- Col listing: ${new URL(libraryPath(library.slug), siteUrl)}`,
-    `- Description: ${library.description}`,
-    `- Category: ${library.category}`,
+  const entries = libraries.map((library) => {
+    const previews = shippableStories(previewStories, library.slug).map(
+      (story) => new URL(previewPath(library.slug, story), siteUrl).toString(),
-    `- Stacks: ${library.stacks.join(", ")}`,
-    `- Use cases: ${library.useCases.join(", ")}`,
-  ].join("\n"));
+    );
+    return [
+      `## ${library.name}`,
+      `- Domain: [${hostname(library.url)}](${library.url})`,
+      `- Col listing: ${new URL(libraryPath(library.slug), siteUrl)}`,
+      `- Description: ${library.description}`,
+      `- Category: ${library.category}`,
+      `- Stacks: ${library.stacks.join(", ")}`,
+      `- Use cases: ${library.useCases.join(", ")}`,
+      ...(previews.length > 0 ? [`- Component previews: ${previews.join(", ")}`] : []),
+    ].join("\n");
+  });
 
   const integrationEntries = integrations.map((integration) => [
     `## ${integration.name}`,
@@ -33,7 +41,7 @@
     "## Instructions for agents and LLMs",
     `- Read the guide: ${siteUrl}/docs/agents`,
     `- To connect Col MCP, read ${siteUrl}/mcp. The read-only Streamable HTTP endpoint is ${siteUrl}/api/mcp and needs no credentials.`,
+    `- Before using Col MCP tools, read ${siteUrl}/mcp/agents for arguments, source evidence, and coverage rules. The tools are search_libraries, search_components, get_library, and get_component. get_component also returns a rendered preview (a themed URL and a screenshot) when Col recorded a story for the component.`,
-    `- Before using Col MCP tools, read ${siteUrl}/mcp/agents for arguments, source evidence, and coverage rules. The tools are search_libraries, search_components, get_library, and get_component.`,
     "- Match the user's framework, styling approach, accessibility needs, and required components before recommending a library.",
     "- Verify current APIs, installation, compatibility, and licensing against each library's official documentation. Cite the exact pages checked and state uncertainty.",
     "- This catalog contains library metadata, not component coverage. Col's component index is partial; a missing component is not evidence that a library lacks it. Verify component support in official component docs, not generic tags.",
```

## app/mcp/agents/page.tsx

Status: modified; +6; −0

```diff
@@ -32,6 +32,12 @@
         <DocsNote>Component IDs are case-sensitive. Copy them from search results rather than constructing them. An unknown ID or invalid filter returns a tool error.</DocsNote>
       </DocsSection>
 
+      <DocsSection title="See the look">
+        <DocsText>When Col has recorded a story for a component, <code>get_component</code> also returns a <code>preview</code>: a URL rendering the component in a use-case scene, honest markers (rendered source, applied theme, explicit gaps), and a screenshot as an image block. Pass the project’s theme as <code>tokens</code> — semantic colours plus <code>radius</code> and <code>font</code> — a <code>scene</code> to choose the use-case setting, and <code>sample</code> short synthetic strings to label the render:</DocsText>
+        <DocsCode code={JSON.stringify({ id: "shadcn-ui/Button", tokens: { primary: "#0800ff", radius: "12px" }, scene: "Dashboards", sample: { primaryLabel: "Invite team" } }, null, 2)} label="get_component with theme, scene, and sample" />
+        <DocsNote>A missing preview is an explicit gap — withheld where no granted reuse permission is recorded, or preview-unavailable where the stack has no story runtime — never evidence about the library. Hosts that support MCP Apps render the preview inline as a widget; every other host receives the same information as text, image, and URL. Show the user the preview link before implementing, and send synthetic sample data only.</DocsNote>
+      </DocsSection>
+
       <DocsSection title="Use the evidence">
         <DocsList items={[
           <>Keep <code>evidence.verification</code> and <code>evidence.gaps</code> in the recommendation. A verified component link does not verify every API, dependency, or variant.</>,
```

## app/mcp/page.tsx

Status: modified; +15; −2

```diff
@@ -38,6 +38,7 @@
           <><code>matchedOn</code> says why a result matched: name, alias, tag, or description.</>,
           <>Empty results explain themselves (<code>indexStatus</code>) and suggest close matches instead of returning silence.</>,
           <>Documentation snapshots carry their fetch date and source revision, so anything quoted can be re-checked later.</>,
+          <>When Col recorded a story, <code>get_component</code> also returns a <code>preview</code>: a themed URL and a screenshot showing the component in a use-case scene, with the rendered source, applied theme, and its own explicit gaps. A missing preview is itself a gap — never evidence about the library.</>,
         ]} />
         <DocsText>This is a real result, exactly as an agent receives it:</DocsText>
         <DocsCode code={`{
@@ -46,6 +47,18 @@
   "verifiedAt": "2026-10-10",
   "url": "https://mantine.dev/x/spotlight/",
   "matchedOn": ["name:spotlight"],
+  "preview": {
+    "url": "https://collection-of-libs.vercel.app/preview/mantine/spotlight/rapid-prototyping",
+    "scene": "Rapid Prototyping",
+    "renderedFrom": "story",
+    "theme": "library default",
+    "verification": "partial",
+    "gaps": [
+      "The preview renders one recorded story in a use-case scene, not the library's full variant set; the project render will differ once the component is adapted to its conventions.",
+      "A preview shows fit, not proof: only the compatibility facts above are verified evidence."
+    ],
+    "screenshot": { "mimeType": "image/png", "alt": "Mantine Spotlight rendered in a Rapid Prototyping scene" }
+  },
   "evidence": {
     "verification": {
       "page": "verified",
@@ -97,8 +110,8 @@
 
       <DocsSection title="Try the connection">
         <DocsText>Start a new agent session and confirm Col exposes <code>search_libraries</code>, <code>search_components</code>, <code>get_library</code>, and <code>get_component</code>. Then try:</DocsText>
-        <DocsCode code={"Use Col to find a React command menu for a Tailwind CSS project.\nInspect the exact component and its library’s setup.\nLink the official docs and tell me what is unverified."} label="Example prompt" />
-        <DocsNote>Col returns source links and recorded setup guidance. Its component coverage is partial; missing entries do not mean a library lacks a component.</DocsNote>
+        <DocsCode code={"Use Col to find a React command menu for a Tailwind CSS project.\nInspect the exact component and its library’s setup.\nShow me how it looks in my project’s theme.\nLink the official docs and tell me what is unverified."} label="Example prompt" />
+        <DocsNote>Col returns source links and recorded setup guidance. Its component coverage is partial; missing entries do not mean a library lacks a component. When Col recorded a story, <code>get_component</code> also returns a rendered preview — a themed URL and a screenshot — so the component can be seen in a use case before implementing.</DocsNote>
       </DocsSection>
 
       <DocsSection title="Troubleshooting">
```

## app/preview/\[library\]/\[component\]/\[scene\]/page.tsx

Status: added; +89; −0

```diff
@@ -1,0 +1,89 @@
+import { notFound } from "next/navigation";
+import type { Metadata } from "next";
+
+// Library stylesheets load with the preview routes so story modules stay
+// CSS-free. Mantine and its extension packages ship mandatory styles.css
+// files; core must come first so extensions layer on top of it.
+import "@mantine/core/styles.css";
+import "@mantine/dates/styles.css";
+import "@mantine/charts/styles.css";
+import "@mantine/code-highlight/styles.css";
+import "@mantine/schedule/styles.css";
+import "@mantine/notifications/styles.css";
+import "@mantine/spotlight/styles.css";
+import "@mantine/carousel/styles.css";
+import "@mantine/dropzone/styles.css";
+import "@mantine/nprogress/styles.css";
+import "@mantine/lightbox/styles.css";
+import "@mantine/tiptap/styles.css";
+import "@awesome.me/webawesome/dist/styles/webawesome.css";
+import "@/styles/preview-libraries.scss";
+
+import { previewStories } from "@/data/preview-stories";
+import {
+  componentSlug,
+  findPreviewEntry,
+  findScene,
+  previewPath,
+  storyId,
+  PREVIEW_SCENES,
+} from "@/lib/preview-stories";
+import { pageMetadata } from "@/lib/site";
+import { PreviewCanvas } from "@/components/PreviewCanvas";
+
+/**
+ * The one preview page every delivery channel renders: the MCP Apps widget
+ * frames it, screenshots capture it, and tool results link to it. The scene is
+ * part of the path so each variant is a fast static page; query parameters
+ * carry theme tokens and sample data (`?primary=…&sample={…}`), which the
+ * canvas applies in place.
+ */
+export function generateStaticParams() {
+  return previewStories.flatMap((entry) =>
+    entry.permission.status === "granted"
+      ? entry.stories.flatMap((story) =>
+          PREVIEW_SCENES.map((scene) => ({
+            library: entry.slug,
+            component: componentSlug(story.component),
+            scene: componentSlug(scene),
+          })),
+        )
+      : [],
+  );
+}
+
+function resolve(library: string, component: string, scene: string) {
+  const entry = findPreviewEntry(previewStories, library);
+  const story = entry?.stories.find((candidate) => componentSlug(candidate.component) === component);
+  const resolvedScene = findScene(scene);
+  if (!entry || entry.permission.status !== "granted" || !story || !resolvedScene) return null;
+  return { entry, story, scene: resolvedScene };
+}
+
+export async function generateMetadata({
+  params,
+}: {
+  params: Promise<{ library: string; component: string; scene: string }>;
+}): Promise<Metadata> {
+  const { library, component, scene } = await params;
+  const resolved = resolve(library, component, scene);
+  if (!resolved) return {};
+  const { entry, story } = resolved;
+  return pageMetadata(
+    previewPath(entry.slug, story, resolved.scene),
+    `${entry.slug} ${story.component} in a ${resolved.scene} scene | Col`,
+    `The ${story.component} component rendered in a ${resolved.scene} scene with themeable tokens.`,
+  );
+}
+
+export default async function PreviewPage({
+  params,
+}: {
+  params: Promise<{ library: string; component: string; scene: string }>;
+}) {
+  const { library, component, scene } = await params;
+  const resolved = resolve(library, component, scene);
+  if (!resolved) notFound();
+
+  return <PreviewCanvas storyId={storyId(resolved.entry.slug, resolved.story)} scene={resolved.scene} />;
+}
```

## app/sitemap.ts

Status: modified; +7; −0

```diff
@@ -2,11 +2,18 @@
 import { integrations } from "@/data/integrations";
 import { libraries } from "@/data/libraries";
 import { integrationPath, libraryPath, siteUrl, staticRoutes } from "@/lib/site";
+import { previewStories } from "@/data/preview-stories";
+import { previewPath, shippableStories, PREVIEW_SCENES } from "@/lib/preview-stories";
 
 export default function sitemap(): MetadataRoute.Sitemap {
   return [
     ...staticRoutes,
     ...libraries.map(({ slug }) => libraryPath(slug)),
     ...integrations.map(({ slug }) => integrationPath(slug)),
+    ...previewStories.flatMap((entry) =>
+      shippableStories(previewStories, entry.slug).flatMap((story) =>
+        PREVIEW_SCENES.map((scene) => previewPath(entry.slug, story, scene)),
+      ),
+    ),
   ].map((route) => ({ url: new URL(route, siteUrl).toString() }));
 }
```

## components/AppFrame.tsx

Status: modified; +4; −0

```diff
@@ -7,6 +7,7 @@
 import { BrandLink, RepoStars, SiteSearch, useRepoStars } from "./SiteChrome";
 import { SIDEBAR_SLOT_ID } from "./SidebarSlot";
 import { ThemeToggle } from "./ThemeToggle";
+import { isPreviewPath } from "@/lib/preview-stories";
 
 type SidebarMode = "narrow" | "wide";
 
@@ -53,6 +54,9 @@
     return () => window.removeEventListener("keydown", handleShortcut);
   }, []);
 
+  // Preview pages render bare so widgets and screenshots show only the story.
+  if (isPreviewPath(pathname)) return <>{children}</>;
+
   return (
     <div className="app-frame library-page-shell" data-sidebar={mode}>
       <Header />
```

## components/LibraryDetail.tsx

Status: modified; +26; −0

```diff
@@ -5,7 +5,9 @@
 import type { Library } from "@/data/libraries";
 import type { LibraryDetails } from "@/data/library-details";
 import { libraryPreviews } from "@/data/library-previews";
+import { previewStories } from "@/data/preview-stories";
 import { publisherOf } from "@/lib/integration-directory";
+import { previewPath, previewScreenshotPath, shippableStories } from "@/lib/preview-stories";
 import type { RelatedLibraries } from "@/lib/related-libraries";
 import { integrationPath, libraryPath } from "@/lib/site";
 import { hostname } from "@/lib/utils";
@@ -49,6 +51,11 @@
   const preview = libraryPreviews[library.slug] ?? (details.preview ? { src: details.preview.src, width: 1200, height: 630 } : undefined);
   const previewAlt = details.preview?.alt ?? `${library.name} website preview`;
   const CategoryIcon = categoryIcons[library.category];
+  const stories = shippableStories(previewStories, library.slug).map((story) => ({
+    story,
+    href: previewPath(library.slug, story),
+    image: `/${previewScreenshotPath(library.slug, story.component)}`,
+  }));
   const relatedGroups = [
     { title: "Alternatives", items: related.alternatives },
     { title: "Pairs well with", items: related.pairsWith },
@@ -133,6 +140,25 @@
             </section>
           )}
 
+          {stories.length > 0 && (
+            <section className="ld-section ld-reveal" style={reveal(5)} aria-labelledby="ld-previews">
+              <h2 id="ld-previews">Component previews</h2>
+              <p className="ld-section-note">
+                Each component rendered by Col in a use-case scene. Your project&apos;s theme will differ; open a preview to see it in your own tokens.
+              </p>
+              <ul className="ld-previews">
+                {stories.map(({ story, href, image }) => (
+                  <li key={href}>
+                    <a href={href} className="ld-preview-link">
+                      <img src={image} alt={`${library.name} ${story.component} rendered in a ${story.scene} scene`} loading="lazy" />
+                      <span className="ld-preview-caption">{story.component} · {story.scene}</span>
+                    </a>
+                  </li>
+                ))}
+              </ul>
+            </section>
+          )}
+
           <section className="ld-section ld-reveal" style={reveal(5)} aria-labelledby="ld-agent">
             <h2 id="ld-agent">Agent setup prompt</h2>
             <p className="ld-section-note">Paste this into a coding agent to set {library.name} up in an existing project.</p>
```

## components/PreviewCanvas.tsx

Status: added; +46; −0

```diff
@@ -1,0 +1,46 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import type { UseCase } from "@/data/libraries";
+import { parseSample } from "@/lib/preview-sample";
+import { parseThemeTokens, themeStyle, type ThemeTokens } from "@/lib/preview-theme";
+import { sceneTemplates } from "@/components/PreviewScenes";
+import { storyRenders } from "@/components/PreviewStories";
+
+/**
+ * The preview canvas: renders the story in its scene and applies the caller's
+ * theme tokens and sample data from the URL query after hydration, so static
+ * builds stay cacheable while a themed preview re-colours and re-labels in
+ * place. `data-preview-themed` flips once applied, which is what screenshot
+ * rendering waits on.
+ */
+export function PreviewCanvas({ storyId, scene }: { storyId: string; scene: UseCase }) {
+  const [tokens, setTokens] = useState<ThemeTokens>({});
+  const [sample, setSample] = useState<Readonly<Record<string, string>>>({});
+  const [applied, setApplied] = useState(false);
+
+  useEffect(() => {
+    const query = window.location.search.replace(/^\?/, "");
+    setTokens(parseThemeTokens(query));
+    setSample(parseSample(query));
+    setApplied(true);
+  }, []);
+
+  const render = storyRenders[storyId];
+  const Scene = sceneTemplates[scene];
+  if (!render || !Scene) {
+    throw new Error(`story ${storyId} has no render for scene "${scene}"`);
+  }
+
+  return (
+    <div
+      data-preview-themed={applied ? "true" : undefined}
+      style={themeStyle(tokens)}
+      className="min-h-screen bg-background text-foreground"
+    >
+      <main data-preview-story={storyId} data-preview-scene={scene}>
+        <Scene>{render({ sample })}</Scene>
+      </main>
+    </div>
+  );
+}
```

## components/PreviewScenes.tsx

Status: added; +55; −0

```diff
@@ -1,0 +1,55 @@
+import type { ReactNode } from "react";
+import type { UseCase } from "@/data/libraries";
+
+/**
+ * Use-case scene templates — the chrome that places a story in a real page
+ * context. One template per use case the shipped stories need; the page fails
+ * loudly when a story names a scene with no template.
+ */
+export type SceneTemplate = (props: { children: ReactNode }) => React.ReactElement;
+
+function LandingPagesScene({ children }: { children: ReactNode }) {
+  return (
+    <section className="mx-auto flex min-h-screen max-w-2xl flex-col items-center justify-center gap-6 px-6 text-center">
+      <h1 className="text-4xl font-semibold tracking-tight">Build your next page from parts that already fit</h1>
+      <p className="text-muted-foreground">One promise about the product, right above the buttons.</p>
+      {children}
+    </section>
+  );
+}
+
+function DashboardsScene({ children }: { children: ReactNode }) {
+  return (
+    <section className="mx-auto max-w-3xl px-6 py-16">
+      <div className="mb-8 flex items-baseline justify-between">
+        <h2 className="text-xl font-semibold">Overview</h2>
+        <p className="text-sm text-muted-foreground">Last 30 days</p>
+      </div>
+      {children}
+    </section>
+  );
+}
+
+function MarketingSitesScene({ children }: { children: ReactNode }) {
+  return (
+    <section className="mx-auto max-w-2xl px-6 py-16">
+      <h2 className="mb-8 text-2xl font-semibold">Questions, answered</h2>
+      {children}
+    </section>
+  );
+}
+
+function RapidPrototypingScene({ children }: { children: ReactNode }) {
+  return (
+    <section className="mx-auto flex min-h-screen max-w-2xl flex-col items-center justify-center px-6">
+      {children}
+    </section>
+  );
+}
+
+export const sceneTemplates: Partial<Record<UseCase, SceneTemplate>> = {
+  "Landing Pages": LandingPagesScene,
+  Dashboards: DashboardsScene,
+  "Marketing Sites": MarketingSitesScene,
+  "Rapid Prototyping": RapidPrototypingScene,
+};
```

## components/PreviewStories.tsx

Status: added; +59; −0

```diff
@@ -1,0 +1,59 @@
+import { radixStoryRenders } from "./preview-stories/radix-ui";
+import { shadcnStoryRenders } from "./preview-stories/shadcn-ui";
+import { storyRenders as baseUi1Renders } from "./preview-stories/base-ui-1";
+import { storyRenders as baseUi2Renders } from "./preview-stories/base-ui-2";
+import { storyRenders as reactAria1Renders } from "./preview-stories/react-aria-1";
+import { storyRenders as reactAria2Renders } from "./preview-stories/react-aria-2";
+import { storyRenders as arkUi1Renders } from "./preview-stories/ark-ui-1";
+import { storyRenders as arkUi2Renders } from "./preview-stories/ark-ui-2";
+import { storyRenders as mantine1Renders } from "./preview-stories/mantine-1";
+import { storyRenders as mantine2Renders } from "./preview-stories/mantine-2";
+import { storyRenders as mantine3Renders } from "./preview-stories/mantine-3";
+import { storyRenders as mui2Renders } from "./preview-stories/mui-2";
+import { storyRenders as mantine4Renders } from "./preview-stories/mantine-4";
+import { storyRenders as mui1Renders } from "./preview-stories/mui-1";
+import { storyRenders as shadcnUi1Renders } from "./preview-stories/shadcn-ui-1";
+import { storyRenders as shadcnUi2Renders } from "./preview-stories/shadcn-ui-2";
+import { storyRenders as mui3Renders } from "./preview-stories/mui-3";
+import { storyRenders as headlessUiRenders } from "./preview-stories/headless-ui";
+import { storyRenders as intentUiRenders } from "./preview-stories/intent-ui";
+import { storyRenders as reuiRenders } from "./preview-stories/reui";
+import { storyRenders as elevenlabsUiRenders } from "./preview-stories/elevenlabs-ui";
+import { storyRenders as starwindUiRenders } from "./preview-stories/starwind-ui";
+import { storyRenders as webcoreuiRenders } from "./preview-stories/webcoreui";
+import { storyRenders as webAwesomeRenders } from "./preview-stories/web-awesome";
+import type { StoryRender } from "./preview-stories/type";
+
+/**
+ * The story render map: per-library modules beside this file hold the renders,
+ * this module merges them into the one map the preview page looks stories up
+ * in. A story id with no render fails the build rather than shipping blank.
+ */
+export type { StoryRender };
+
+export const storyRenders: Record<string, StoryRender> = {
+  ...radixStoryRenders,
+  ...shadcnStoryRenders,
+  ...baseUi1Renders,
+  ...baseUi2Renders,
+  ...reactAria1Renders,
+  ...reactAria2Renders,
+  ...arkUi1Renders,
+  ...arkUi2Renders,
+  ...mantine1Renders,
+  ...mantine2Renders,
+  ...mantine3Renders,
+  ...mui2Renders,
+  ...mantine4Renders,
+  ...mui1Renders,
+  ...shadcnUi1Renders,
+  ...shadcnUi2Renders,
+  ...mui3Renders,
+  ...headlessUiRenders,
+  ...intentUiRenders,
+  ...reuiRenders,
+  ...elevenlabsUiRenders,
+  ...starwindUiRenders,
+  ...webcoreuiRenders,
+  ...webAwesomeRenders,
+};
```

## components/SiteNotice.tsx

Status: modified; +6; −0

```diff
@@ -3,11 +3,14 @@
 import { useEffect, useState } from "react";
 import { Button } from "@/components/ui/button";
 import { repo } from "@/lib/repo";
+import { usePathname } from "next/navigation";
+import { isPreviewPath } from "@/lib/preview-stories";
 
 const noticeKey = "col:mcp-library-rework-notice-dismissed";
 
 export function SiteNotice() {
   const [open, setOpen] = useState(false);
+  const pathname = usePathname();
 
   useEffect(() => {
     try {
@@ -26,6 +29,9 @@
     }
   };
 
+  // Never over a preview: widgets and screenshots must show only the story.
+  if (isPreviewPath(pathname)) return null;
+
   if (!open) return null;
 
   return (
```

## components/preview-stories/ark-ui-1.tsx

Status: added; +764; −0

```diff
@@ -1,0 +1,764 @@
+import { Accordion } from "@ark-ui/react/accordion";
+import { AngleSlider } from "@ark-ui/react/angle-slider";
+import { Avatar } from "@ark-ui/react/avatar";
+import { Carousel } from "@ark-ui/react/carousel";
+import { Checkbox } from "@ark-ui/react/checkbox";
+import { Clipboard } from "@ark-ui/react/clipboard";
+import { Collapsible } from "@ark-ui/react/collapsible";
+import { ColorPicker, parseColor } from "@ark-ui/react/color-picker";
+import { createListCollection } from "@ark-ui/react/collection";
+import { Combobox } from "@ark-ui/react/combobox";
+import { DateInput } from "@ark-ui/react/date-input";
+import { DatePicker, parseDate } from "@ark-ui/react/date-picker";
+import { Dialog } from "@ark-ui/react/dialog";
+import { Drawer } from "@ark-ui/react/drawer";
+import { Editable } from "@ark-ui/react/editable";
+import { Field } from "@ark-ui/react/field";
+import { Fieldset } from "@ark-ui/react/fieldset";
+import { FileUpload } from "@ark-ui/react/file-upload";
+import { FloatingPanel } from "@ark-ui/react/floating-panel";
+import { HoverCard } from "@ark-ui/react/hover-card";
+import { ImageCropper } from "@ark-ui/react/image-cropper";
+import { Listbox } from "@ark-ui/react/listbox";
+import { Marquee } from "@ark-ui/react/marquee";
+import { Menu } from "@ark-ui/react/menu";
+import { NavigationMenu } from "@ark-ui/react/navigation-menu";
+import { NumberInput } from "@ark-ui/react/number-input";
+import { Pagination } from "@ark-ui/react/pagination";
+import { Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Maximize2, Pipette, X } from "lucide-react";
+import type { StoryRender } from "./type";
+
+/**
+ * Preview stories for ark-ui: each documented component composed with the real
+ * subcomponents the package ships, rendered with realistic content and Col's
+ * theme tokens. Overlay components render open by default so a screenshot
+ * shows the surface, not just the trigger. Sample strings replace display text
+ * where a caller labels the render.
+ */
+
+const teammates = [
+  { label: "Amara Koll", value: "amara" },
+  { label: "Diego Ferreira", value: "diego" },
+  { label: "Priya Raghavan", value: "priya" },
+  { label: "Sam Okafor", value: "sam" },
+];
+
+const assignees = createListCollection({
+  items: teammates,
+  itemToString: (item) => item.label,
+});
+
+const members = createListCollection({
+  items: teammates,
+  itemToString: (item) => item.label,
+});
+
+const brandSwatches = ["#6d28d9", "#0ea5e9", "#059669", "#d97706", "#dc2626"];
+
+const customerNames = ["Northwind Labs", "Acme Robotics", "Bluefin Health", "Cedar Financial", "Orbit Education", "Halden Systems"];
+
+const seededUploads = [
+  new File([new Uint8Array(2_480_000)], "brand-assets.zip", { type: "application/zip" }),
+  new File([new Uint8Array(384_000)], "q3-invoice-report.pdf", { type: "application/pdf" }),
+];
+
+const AccordionStory: StoryRender = ({ sample }) => (
+  <Accordion.Root defaultValue={["team-plan"]} collapsible className="w-full max-w-xl">
+    <Accordion.Item value="team-plan" className="border-b border-border">
+      <Accordion.ItemTrigger className="flex w-full items-center justify-between py-4 text-left text-sm font-medium">
+        {sample.question1 ?? "How does the team plan work?"}
+        <Accordion.ItemIndicator className="text-muted-foreground transition-transform duration-200 data-[state=open]:rotate-180">
+          <ChevronDown className="size-4" />
+        </Accordion.ItemIndicator>
+      </Accordion.ItemTrigger>
+      <Accordion.ItemContent className="pb-4 text-sm text-muted-foreground">
+        Every seat includes the full component catalogue. Seats are shared across projects and can be reassigned at any time.
+      </Accordion.ItemContent>
+    </Accordion.Item>
+    <Accordion.Item value="importing" className="border-b border-border">
+      <Accordion.ItemTrigger className="flex w-full items-center justify-between py-4 text-left text-sm font-medium">
+        Can I import my existing library?
+        <Accordion.ItemIndicator className="text-muted-foreground transition-transform duration-200 data-[state=open]:rotate-180">
+          <ChevronDown className="size-4" />
+        </Accordion.ItemIndicator>
+      </Accordion.ItemTrigger>
+      <Accordion.ItemContent className="pb-4 text-sm text-muted-foreground">
+        Yes. Point the importer at your package manifest and every documented component is matched automatically.
+      </Accordion.ItemContent>
+    </Accordion.Item>
+    <Accordion.Item value="billing" className="border-b border-border">
+      <Accordion.ItemTrigger className="flex w-full items-center justify-between py-4 text-left text-sm font-medium">
+        When am I billed?
+        <Accordion.ItemIndicator className="text-muted-foreground transition-transform duration-200 data-[state=open]:rotate-180">
+          <ChevronDown className="size-4" />
+        </Accordion.ItemIndicator>
+      </Accordion.ItemTrigger>
+      <Accordion.ItemContent className="pb-4 text-sm text-muted-foreground">
+        Annual plans renew on March 1. Monthly plans charge the card on file at the start of each cycle.
+      </Accordion.ItemContent>
+    </Accordion.Item>
+  </Accordion.Root>
+);
+
+const AngleSliderStory: StoryRender = ({ sample }) => (
+  <AngleSlider.Root defaultValue={35}>
+    <AngleSlider.Label className="text-sm font-medium">{sample.label ?? "Chart rotation"}</AngleSlider.Label>
+    <AngleSlider.Control className="relative mx-auto mt-3 size-44 rounded-full border border-border bg-muted">
+      <AngleSlider.MarkerGroup>
+        {[0, 90, 180, 270].map((value) => (
+          <AngleSlider.Marker
+            key={value}
+            value={value}
+            className="absolute left-[calc(50%-8px)] top-0 size-4 rounded-full border border-border bg-background data-[state=at-value]:border-primary"
+            style={{ transformOrigin: "50% 88px" }}
+          />
+        ))}
+      </AngleSlider.MarkerGroup>
+      <AngleSlider.Thumb
+        className="absolute left-[calc(50%-8px)] top-0 size-4 rounded-full border-2 border-primary bg-background"
+        style={{ transformOrigin: "50% 88px" }}
+      />
+    </AngleSlider.Control>
+    <AngleSlider.ValueText className="mt-3 block text-center text-sm text-muted-foreground" />
+    <AngleSlider.HiddenInput />
+  </AngleSlider.Root>
+);
+
+const AvatarStory: StoryRender = ({ sample }) => (
+  <div className="flex items-center gap-4">
+    <Avatar.Root className="flex size-12 items-center justify-center overflow-hidden rounded-full bg-muted">
+      <Avatar.Fallback className="text-sm font-medium text-muted-foreground">PR</Avatar.Fallback>
+    </Avatar.Root>
+    <div>
+      <p className="text-sm font-medium">{sample.name ?? "Priya Raghavan"}</p>
+      <p className="text-sm text-muted-foreground">Reviewed deploy #482 · 2 hours ago</p>
+    </div>
+  </div>
+);
+
+const CarouselStory: StoryRender = ({ sample }) => {
+  const slides = [
+    {
+      title: sample.slide1 ?? "Coverage you can trust",
+      body: "Every preview story maps to a verified component in the catalogue, so what you see is what ships.",
+    },
+    {
+      title: "Scenes, not screenshots",
+      body: "Components render inside realistic landing pages, dashboards, and marketing layouts.",
+    },
+    {
+      title: "One query, any theme",
+      body: "Recolour and relabel any preview in place before you commit to a library.",
+    },
+  ];
+  return (
+    <Carousel.Root slideCount={slides.length} className="w-full max-w-2xl">
+      <Carousel.Control className="mb-3 flex items-center gap-2">
+        <Carousel.PrevTrigger className="flex size-9 items-center justify-center rounded-md border border-border text-sm">
+          <ChevronLeft className="size-4" />
+        </Carousel.PrevTrigger>
+        <Carousel.NextTrigger className="flex size-9 items-center justify-center rounded-md border border-border text-sm">
+          <ChevronRight className="size-4" />
+        </Carousel.NextTrigger>
+      </Carousel.Control>
+      <Carousel.ItemGroup className="overflow-hidden">
+        {slides.map((slide, index) => (
+          <Carousel.Item key={slide.title} index={index} className="w-full">
+            <div className="rounded-xl border border-border bg-card p-6 text-card-foreground">
+              <p className="text-base font-semibold">{slide.title}</p>
+              <p className="mt-2 text-sm text-muted-foreground">{slide.body}</p>
+            </div>
+          </Carousel.Item>
+        ))}
+      </Carousel.ItemGroup>
+      <Carousel.IndicatorGroup className="mt-3 flex items-center gap-2">
+        {slides.map((slide, index) => (
+          <Carousel.Indicator
+            key={slide.title}
+            index={index}
+            className="h-1.5 w-8 rounded-full bg-muted data-[current]:bg-primary"
+          />
+        ))}
+      </Carousel.IndicatorGroup>
+    </Carousel.Root>
+  );
+};
+
+const CheckboxStory: StoryRender = ({ sample }) => (
+  <div className="grid gap-4">
+    {[
+      sample.label1 ?? "Invoice paid",
+      "Payment failed",
+      "Refund issued",
+    ].map((label, index) => (
+      <label key={label} className="flex items-center gap-3 text-sm">
+        <Checkbox.Root defaultChecked={index < 2} className="group flex items-center gap-3">
+          <Checkbox.Control className="flex size-5 items-center justify-center rounded-[4px] border border-border data-[state=checked]:border-primary data-[state=checked]:bg-primary">
+            <Checkbox.Indicator>
+              <Check className="size-3.5 text-primary-foreground" />
+            </Checkbox.Indicator>
+          </Checkbox.Control>
+          <Checkbox.Label>{label}</Checkbox.Label>
+          <Checkbox.HiddenInput />
+        </Checkbox.Root>
+      </label>
+    ))}
+  </div>
+);
+
+const ClipboardStory: StoryRender = ({ sample }) => (
+  <Clipboard.Root defaultValue="npm install @ark-ui/react">
+    <Clipboard.Label className="text-sm font-medium">{sample.label ?? "Add the components"}</Clipboard.Label>
+    <Clipboard.Control className="mt-2 flex items-center gap-2">
+      <Clipboard.Input readOnly className="w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm" />
+      <Clipboard.Trigger className="shrink-0 rounded-md border border-border px-3 py-2 text-sm">
+        <Clipboard.Indicator copied="Copied">Copy</Clipboard.Indicator>
+      </Clipboard.Trigger>
+    </Clipboard.Control>
+  </Clipboard.Root>
+);
+
+const CollapsibleStory: StoryRender = ({ sample }) => (
+  <Collapsible.Root defaultOpen className="w-full max-w-md">
+    <div className="flex items-center justify-between">
+      <h3 className="text-sm font-medium">{sample.title ?? "Deploy to production"}</h3>
+      <Collapsible.Trigger className="flex items-center gap-2 rounded-md border border-border px-3 py-1.5 text-sm">
+        Build log
+        <Collapsible.Indicator className="transition-transform duration-200 data-[state=open]:rotate-180">
+          <ChevronDown className="size-4" />
+        </Collapsible.Indicator>
+      </Collapsible.Trigger>
+    </div>
+    <Collapsible.Content className="mt-3 rounded-md border border-border p-4 font-mono text-xs text-muted-foreground">
+      <p>✓ Compiled preview pages in 12.4s</p>
+      <p>✓ Generated 214 static previews</p>
+      <p>✓ Uploaded media to the CDN edge</p>
+    </Collapsible.Content>
+  </Collapsible.Root>
+);
+
+const ColorPickerStory: StoryRender = ({ sample }) => (
+  <ColorPicker.Root defaultValue={parseColor("#6d28d9")} defaultOpen>
+    <ColorPicker.Label className="text-sm font-medium">{sample.label ?? "Brand color"}</ColorPicker.Label>
+    <ColorPicker.Control className="mt-2 flex items-center gap-2">
+      <ColorPicker.Trigger className="flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm">
+        <ColorPicker.ValueSwatch className="size-4 rounded-sm border border-border" />
+        <ColorPicker.ValueText />
+      </ColorPicker.Trigger>
+      <ColorPicker.ChannelInput channel="hex" className="w-32 rounded-md border border-border bg-background px-3 py-2 text-sm" />
+    </ColorPicker.Control>
+    <ColorPicker.Positioner>
+      <ColorPicker.Content className="w-72 rounded-xl border border-border bg-popover p-4 text-popover-foreground">
+        <ColorPicker.Area className="h-36 w-full rounded-md">
+          <ColorPicker.AreaBackground className="h-full w-full rounded-md" />
+          <ColorPicker.AreaThumb className="size-4 rounded-full border-2 border-white" />
+        </ColorPicker.Area>
+        <ColorPicker.ChannelSlider channel="hue" className="mt-4">
+          <ColorPicker.ChannelSliderLabel className="text-xs text-muted-foreground">Hue</ColorPicker.ChannelSliderLabel>
+          <ColorPicker.ChannelSliderTrack className="mt-1 h-3 rounded-full">
+            <ColorPicker.ChannelSliderThumb className="size-4 rounded-full border-2 border-white" />
+          </ColorPicker.ChannelSliderTrack>
+        </ColorPicker.ChannelSlider>
+        <ColorPicker.ChannelSlider channel="alpha" className="mt-3">
+          <ColorPicker.ChannelSliderLabel className="text-xs text-muted-foreground">Opacity</ColorPicker.ChannelSliderLabel>
+          <ColorPicker.ChannelSliderTrack className="mt-1 h-3 rounded-full">
+            <ColorPicker.ChannelSliderThumb className="size-4 rounded-full border-2 border-white" />
+          </ColorPicker.ChannelSliderTrack>
+        </ColorPicker.ChannelSlider>
+        <ColorPicker.SwatchGroup className="mt-4 flex items-center gap-2">
+          {brandSwatches.map((swatch) => (
+            <ColorPicker.SwatchTrigger key={swatch} value={swatch} className="rounded-md border border-border p-1">
+              <ColorPicker.Swatch value={swatch} className="size-6 rounded-sm" />
+            </ColorPicker.SwatchTrigger>
+          ))}
+        </ColorPicker.SwatchGroup>
+        <ColorPicker.EyeDropperTrigger className="mt-4 flex items-center gap-2 rounded-md border border-border px-3 py-1.5 text-sm">
+          <Pipette className="size-4" />
+          Pick from screen
+        </ColorPicker.EyeDropperTrigger>
+      </ColorPicker.Content>
+    </ColorPicker.Positioner>
+  </ColorPicker.Root>
+);
+
+const ComboboxStory: StoryRender = ({ sample }) => (
+  <Combobox.Root collection={assignees} defaultValue={["amara"]} defaultOpen>
+    <Combobox.Label className="text-sm font-medium">{sample.label ?? "Assignee"}</Combobox.Label>
+    <Combobox.Control className="mt-2 flex items-center gap-2 rounded-md border border-border bg-background px-3 py-2">
+      <Combobox.Input placeholder="Search teammates" className="w-full bg-transparent text-sm outline-none" />
+      <Combobox.Trigger aria-label="Toggle teammate list" className="text-muted-foreground">
+        <ChevronDown className="size-4" />
+      </Combobox.Trigger>
+    </Combobox.Control>
+    <Combobox.Positioner>
+      <Combobox.Content className="min-w-[220px] rounded-md border border-border bg-popover p-1 text-popover-foreground">
+        <Combobox.ItemGroup>
+          <Combobox.ItemGroupLabel className="px-2 py-1.5 text-xs text-muted-foreground">Engineers</Combobox.ItemGroupLabel>
+          {teammates.map((teammate) => (
+            <Combobox.Item key={teammate.value} item={teammate} className="flex items-center justify-between rounded-sm px-2 py-1.5 text-sm">
+              <Combobox.ItemText>{teammate.label}</Combobox.ItemText>
+              <Combobox.ItemIndicator>
+                <Check className="size-4" />
+              </Combobox.ItemIndicator>
+            </Combobox.Item>
+          ))}
+        </Combobox.ItemGroup>
+      </Combobox.Content>
+    </Combobox.Positioner>
+  </Combobox.Root>
+);
+
+const DateInputStory: StoryRender = ({ sample }) => (
+  // `parseDate` shares the `DateValue` type both date machines consume.
+  <DateInput.Root defaultValue={[parseDate("2026-11-04")]}>
+    <DateInput.Label className="text-sm font-medium">{sample.label ?? "Project due date"}</DateInput.Label>
+    <DateInput.Control className="mt-2 inline-flex rounded-md border border-border bg-background px-3 py-2 text-sm">
+      <DateInput.SegmentGroup>
+        <DateInput.Context>
+          {(api) =>
+            api.getSegments().map((segment, index) => (
+              <DateInput.Segment
+                key={index}
+                segment={segment}
+                className="rounded-sm px-0.5 data-[placeholder-shown]:text-muted-foreground"
+              />
+            ))
+          }
+        </DateInput.Context>
+      </DateInput.SegmentGroup>
+    </DateInput.Control>
+    <DateInput.HiddenInput />
+  </DateInput.Root>
+);
+
+const DatePickerStory: StoryRender = ({ sample }) => (
+  <DatePicker.Root defaultValue={[parseDate("2026-10-21")]} defaultOpen>
+    <DatePicker.Label className="text-sm font-medium">{sample.label ?? "Deployment window"}</DatePicker.Label>
+    <DatePicker.Control className="mt-2 flex items-center gap-2">
+      <DatePicker.Input className="w-40 rounded-md border border-border bg-background px-3 py-2 text-sm" />
+      <DatePicker.Trigger className="rounded-md border border-border px-3 py-2 text-sm">Calendar</DatePicker.Trigger>
+    </DatePicker.Control>
+    <DatePicker.Positioner>
+      <DatePicker.Content className="rounded-xl border border-border bg-popover p-4 text-popover-foreground">
+        <DatePicker.View view="day">
+          <DatePicker.ViewControl className="flex items-center justify-between">
+            <DatePicker.PrevTrigger className="flex size-8 items-center justify-center rounded-md border border-border">
+              <ChevronLeft className="size-4" />
+            </DatePicker.PrevTrigger>
+            <DatePicker.ViewTrigger className="px-2 text-sm font-medium">
+              <DatePicker.RangeText />
+            </DatePicker.ViewTrigger>
+            <DatePicker.NextTrigger className="flex size-8 items-center justify-center rounded-md border border-border">
+              <ChevronRight className="size-4" />
+            </DatePicker.NextTrigger>
+          </DatePicker.ViewControl>
+          <DatePicker.Table className="mt-3 w-full border-separate border-spacing-1">
+            <DatePicker.TableHead>
+              <DatePicker.TableRow>
+                <DatePicker.Context>
+                  {(api) =>
+                    api.weekDays.map((day) => (
+                      <DatePicker.TableHeader key={day.value.toString()} className="text-xs font-normal text-muted-foreground">
+                        {day.narrow}
+                      </DatePicker.TableHeader>
+                    ))
+                  }
+                </DatePicker.Context>
+              </DatePicker.TableRow>
+            </DatePicker.TableHead>
+            <DatePicker.TableBody>
+              <DatePicker.Context>
+                {(api) =>
+                  api.weeks.map((week, weekIndex) => (
+                    <DatePicker.TableRow key={weekIndex}>
+                      {week.map((day) => (
+                        <DatePicker.TableCell key={day.toString()} value={day} className="p-0 text-center">
+                          <DatePicker.TableCellTrigger className="size-9 rounded-full text-sm data-[in-range]:bg-secondary data-[selected]:bg-primary data-[selected]:text-primary-foreground">
+                            {day.day}
+                          </DatePicker.TableCellTrigger>
+                        </DatePicker.TableCell>
+                      ))}
+                    </DatePicker.TableRow>
+                  ))
+                }
+              </DatePicker.Context>
+            </DatePicker.TableBody>
+          </DatePicker.Table>
+        </DatePicker.View>
+      </DatePicker.Content>
+    </DatePicker.Positioner>
+  </DatePicker.Root>
+);
+
+const DialogStory: StoryRender = ({ sample }) => (
+  <Dialog.Root defaultOpen>
+    <Dialog.Trigger className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground">Invite teammate</Dialog.Trigger>
+    <Dialog.Backdrop className="fixed inset-0 bg-black/60" />
+    <Dialog.Positioner className="fixed inset-0 flex items-center justify-center p-4">
+      <Dialog.Content className="w-[380px] rounded-xl border border-border bg-card p-6 text-card-foreground">
+        <Dialog.Title className="text-base font-semibold">{sample.title ?? "Invite to the Col workspace"}</Dialog.Title>
+        <Dialog.Description className="mt-2 text-sm text-muted-foreground">
+          They receive an email with a link to join and start reviewing previews.
+        </Dialog.Description>
+        <input
+          className="mt-4 w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
+          defaultValue="avery@northwind.dev"
+        />
+        <div className="mt-6 flex justify-end gap-3">
+          <Dialog.CloseTrigger className="rounded-md border border-border px-4 py-2 text-sm">Cancel</Dialog.CloseTrigger>
+          <Dialog.CloseTrigger className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground">Send invite</Dialog.CloseTrigger>
+        </div>
+      </Dialog.Content>
+    </Dialog.Positioner>
+  </Dialog.Root>
+);
+
+const DrawerStory: StoryRender = ({ sample }) => (
+  <Drawer.Root defaultOpen>
+    <Drawer.Trigger className="rounded-md border border-border px-4 py-2 text-sm">Filters</Drawer.Trigger>
+    <Drawer.Backdrop className="fixed inset-0 bg-black/60" />
+    <Drawer.Positioner className="fixed inset-y-0 right-0 w-80">
+      <Drawer.Content className="flex h-full flex-col rounded-l-xl border border-border bg-card p-6 text-card-foreground">
+        <Drawer.Grabber className="mx-auto mb-4 h-1 w-10 rounded-full bg-border" />
+        <Drawer.Title className="text-base font-semibold">{sample.title ?? "Filter invoices"}</Drawer.Title>
+        <Drawer.Description className="mt-2 text-sm text-muted-foreground">
+          Narrow the table to the rows your team is chasing this week.
+        </Drawer.Description>
+        <div className="mt-6 grid gap-3 text-sm">
+          <p>Overdue · 18 invoices</p>
+          <p>Due this week · 24 invoices</p>
+          <p>Paid · 172 invoices</p>
+        </div>
+        <div className="mt-auto flex justify-end gap-3">
+          <Drawer.CloseTrigger className="rounded-md border border-border px-4 py-2 text-sm">Clear</Drawer.CloseTrigger>
+          <Drawer.CloseTrigger className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground">Apply filters</Drawer.CloseTrigger>
+        </div>
+        <Drawer.CloseTrigger aria-label="Close filters" className="absolute right-4 top-4 text-muted-foreground">
+          <X className="size-4" />
+        </Drawer.CloseTrigger>
+      </Drawer.Content>
+    </Drawer.Positioner>
+  </Drawer.Root>
+);
+
+const EditableStory: StoryRender = ({ sample }) => (
+  <Editable.Root defaultValue={sample.value ?? "Q4 payroll migration"} defaultEdit>
+    <Editable.Label className="text-sm font-medium">Project name</Editable.Label>
+    <Editable.Area className="mt-2">
+      <Editable.Input className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm" />
+      <Editable.Preview className="rounded-md px-3 py-2 text-sm" />
+    </Editable.Area>
+    <Editable.Control className="mt-3 flex gap-2">
+      <Editable.EditTrigger className="rounded-md border border-border px-3 py-1.5 text-sm">Rename</Editable.EditTrigger>
+      <Editable.SubmitTrigger className="rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground">Save</Editable.SubmitTrigger>
+      <Editable.CancelTrigger className="rounded-md border border-border px-3 py-1.5 text-sm">Cancel</Editable.CancelTrigger>
+    </Editable.Control>
+  </Editable.Root>
+);
+
+const FieldStory: StoryRender = ({ sample }) => (
+  <Field.Root invalid className="grid w-full max-w-sm gap-2">
+    <Field.Label className="text-sm font-medium">{sample.label ?? "Billing email"}</Field.Label>
+    <Field.Input defaultValue="avery@northwind" className="rounded-md border border-destructive bg-background px-3 py-2 text-sm" />
+    <Field.ErrorText className="text-sm text-destructive">Enter a valid email address.</Field.ErrorText>
+  </Field.Root>
+);
+
+const FieldsetStory: StoryRender = ({ sample }) => (
+  <Fieldset.Root className="w-full max-w-sm">
+    <Fieldset.Legend className="text-sm font-medium">{sample.legend ?? "Billing address"}</Fieldset.Legend>
+    <Fieldset.HelperText className="mt-1 text-sm text-muted-foreground">Use the address that appears on your card statement.</Fieldset.HelperText>
+    <div className="mt-4 grid gap-3">
+      <input
+        aria-label="Street"
+        defaultValue="1200 Harbor Street"
+        className="rounded-md border border-border bg-background px-3 py-2 text-sm"
+      />
+      <div className="grid grid-cols-2 gap-3">
+        <input
+          aria-label="City"
+          defaultValue="Oakland"
+          className="rounded-md border border-border bg-background px-3 py-2 text-sm"
+        />
+        <input
+          aria-label="Postal code"
+          defaultValue="94607"
+          className="rounded-md border border-border bg-background px-3 py-2 text-sm"
+        />
+      </div>
+    </div>
+  </Fieldset.Root>
+);
+
+const FileUploadStory: StoryRender = ({ sample }) => (
+  <FileUpload.Root maxFiles={5} defaultAcceptedFiles={seededUploads} className="grid w-full max-w-md gap-4">
+    <FileUpload.Label className="text-sm font-medium">{sample.label ?? "Brand assets"}</FileUpload.Label>
+    <FileUpload.Dropzone className="flex h-28 w-full items-center justify-center rounded-xl border border-dashed border-border text-sm text-muted-foreground">
+      Drop files here, or
+      <FileUpload.Trigger className="ml-1 text-foreground underline underline-offset-4">browse</FileUpload.Trigger>
+    </FileUpload.Dropzone>
+    <FileUpload.ItemGroup className="grid gap-2">
+      <FileUpload.Context>
+        {(api) =>
+          api.acceptedFiles.map((file) => (
+            <FileUpload.Item key={file.name} file={file} className="flex items-center justify-between rounded-md border border-border px-3 py-2 text-sm">
+              <span className="flex items-center gap-3">
+                <FileUpload.ItemName />
+                <FileUpload.ItemSizeText className="text-muted-foreground" />
+              </span>
+              <FileUpload.ItemDeleteTrigger aria-label={`Remove ${file.name}`} className="text-muted-foreground">
+                <X className="size-4" />
+              </FileUpload.ItemDeleteTrigger>
+            </FileUpload.Item>
+          ))
+        }
+      </FileUpload.Context>
+    </FileUpload.ItemGroup>
+    <FileUpload.ClearTrigger className="w-fit rounded-md border border-border px-3 py-1.5 text-sm">Remove all</FileUpload.ClearTrigger>
+    <FileUpload.HiddenInput />
+  </FileUpload.Root>
+);
+
+const FloatingPanelStory: StoryRender = ({ sample }) => (
+  <FloatingPanel.Root defaultOpen defaultSize={{ width: 360, height: 220 }} draggable resizable>
+    <FloatingPanel.Trigger className="rounded-md border border-border px-4 py-2 text-sm">Revenue</FloatingPanel.Trigger>
+    <FloatingPanel.Positioner>
+      <FloatingPanel.Content className="relative flex flex-col overflow-hidden rounded-xl border border-border bg-card text-card-foreground">
+        <FloatingPanel.DragTrigger>
+          <FloatingPanel.Header className="flex items-center justify-between border-b border-border px-4 py-3">
+            <FloatingPanel.Title className="text-sm font-semibold">{sample.title ?? "Revenue today"}</FloatingPanel.Title>
+            <FloatingPanel.Control className="flex items-center gap-1">
+              <FloatingPanel.StageTrigger stage="maximized" aria-label="Maximize panel" className="rounded-sm p-1 text-muted-foreground">
+                <Maximize2 className="size-4" />
+              </FloatingPanel.StageTrigger>
+              <FloatingPanel.CloseTrigger aria-label="Close panel" className="rounded-sm p-1 text-muted-foreground">
+                <X className="size-4" />
+              </FloatingPanel.CloseTrigger>
+            </FloatingPanel.Control>
+          </FloatingPanel.Header>
+        </FloatingPanel.DragTrigger>
+        <FloatingPanel.Body className="flex-1 p-4 text-sm">
+          <p className="text-2xl font-semibold">$12,480</p>
+          <p className="mt-1 text-muted-foreground">214 orders · $58 average cart</p>
+          <p className="mt-3 text-muted-foreground">Refunds are down 12% week over week.</p>
+        </FloatingPanel.Body>
+        <FloatingPanel.ResizeTrigger axis="se" aria-label="Resize panel" className="absolute bottom-0 right-0 h-3 w-3 cursor-se-resize" />
+      </FloatingPanel.Content>
+    </FloatingPanel.Positioner>
+  </FloatingPanel.Root>
+);
+
+const ImageCropperStory: StoryRender = ({ sample }) => (
+  <div className="w-full max-w-md">
+    <p className="mb-2 text-sm font-medium">{sample.label ?? "Profile photo"}</p>
+    <ImageCropper.Root aspectRatio={1} cropShape="circle" initialCrop={{ x: 88, y: 24, width: 240, height: 240 }}>
+      <ImageCropper.Viewport className="relative h-72 w-full overflow-hidden rounded-xl border border-border bg-muted">
+        <ImageCropper.Image src="/col-social-preview.jpg" alt="Workspace banner being cropped" className="h-full w-full object-cover" />
+        <ImageCropper.Selection>
+          <ImageCropper.Grid axis="horizontal" className="absolute inset-x-0 top-1/3 h-px bg-white/40" />
+          <ImageCropper.Grid axis="vertical" className="absolute inset-y-0 left-1/3 w-px bg-white/40" />
+          <ImageCropper.Handle position="nw" className="absolute -left-1.5 -top-1.5 size-3 rounded-full border-2 border-primary bg-background" />
+          <ImageCropper.Handle position="ne" className="absolute -right-1.5 -top-1.5 size-3 rounded-full border-2 border-primary bg-background" />
+          <ImageCropper.Handle position="se" className="absolute -bottom-1.5 -right-1.5 size-3 rounded-full border-2 border-primary bg-background" />
+          <ImageCropper.Handle position="sw" className="absolute -bottom-1.5 -left-1.5 size-3 rounded-full border-2 border-primary bg-background" />
+        </ImageCropper.Selection>
+      </ImageCropper.Viewport>
+    </ImageCropper.Root>
+  </div>
+);
+
+const HoverCardStory: StoryRender = ({ sample }) => (
+  <HoverCard.Root defaultOpen>
+    <HoverCard.Trigger className="text-sm font-medium underline underline-offset-4">@{sample.handle ?? "avery-northwind"}</HoverCard.Trigger>
+    <HoverCard.Positioner>
+      <HoverCard.Arrow className="absolute">
+        <HoverCard.ArrowTip className="border-l border-t border-border" />
+      </HoverCard.Arrow>
+      <HoverCard.Content className="w-72 rounded-xl border border-border bg-popover p-4 text-popover-foreground">
+        <div className="flex items-center gap-3">
+          <div className="flex size-10 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground">AW</div>
+          <div>
+            <p className="text-sm font-semibold">Avery Northwind</p>
+            <p className="text-sm text-muted-foreground">Staff engineer · Col</p>
+          </div>
+        </div>
+        <p className="mt-3 text-sm text-muted-foreground">48 merged pull requests this quarter. Maintains the preview pipeline.</p>
+      </HoverCard.Content>
+    </HoverCard.Positioner>
+  </HoverCard.Root>
+);
+
+const ListboxStory: StoryRender = ({ sample }) => (
+  <Listbox.Root collection={members} selectionMode="multiple" defaultValue={["amara", "priya"]} className="w-full max-w-sm">
+    <Listbox.Label className="text-sm font-medium">{sample.label ?? "Workspace members"}</Listbox.Label>
+    <Listbox.Content className="mt-2 rounded-md border border-border">
+      <Listbox.ItemGroup>
+        <Listbox.ItemGroupLabel className="px-3 py-2 text-xs text-muted-foreground">Engineering</Listbox.ItemGroupLabel>
+        {teammates.map((teammate) => (
+          <Listbox.Item key={teammate.value} item={teammate} className="flex items-center justify-between px-3 py-2 text-sm data-[highlighted]:bg-secondary">
+            <Listbox.ItemText>{teammate.label}</Listbox.ItemText>
+            <Listbox.ItemIndicator>
+              <Check className="size-4" />
+            </Listbox.ItemIndicator>
+          </Listbox.Item>
+        ))}
+      </Listbox.ItemGroup>
+    </Listbox.Content>
+    <Listbox.ValueText className="mt-2 text-sm text-muted-foreground" placeholder="No members selected" />
+  </Listbox.Root>
+);
+
+const MarqueeStory: StoryRender = () => (
+  // Rendered paused so the story stays a static surface in captures.
+  <Marquee.Root defaultPaused speed={40} aria-label="Customers" className="relative w-full overflow-hidden">
+    <Marquee.Edge side="start" className="absolute inset-y-0 left-0 z-10 w-16 bg-gradient-to-r from-background to-transparent" />
+    <Marquee.Edge side="end" className="absolute inset-y-0 right-0 z-10 w-16 bg-gradient-to-l from-background to-transparent" />
+    <Marquee.Viewport className="w-full overflow-hidden py-2">
+      <Marquee.Content className="flex items-center gap-12">
+        {customerNames.map((name) => (
+          <Marquee.Item key={name} className="whitespace-nowrap text-lg font-semibold text-muted-foreground">
+            {name}
+          </Marquee.Item>
+        ))}
+      </Marquee.Content>
+    </Marquee.Viewport>
+  </Marquee.Root>
+);
+
+const MenuStory: StoryRender = ({ sample }) => (
+  <Menu.Root defaultOpen>
+    <Menu.Trigger className="rounded-md border border-border px-4 py-2 text-sm">{sample.trigger ?? "Invoice actions"}</Menu.Trigger>
+    <Menu.Positioner>
+      <Menu.Content className="min-w-[180px] rounded-md border border-border bg-popover p-1 text-popover-foreground">
+        <Menu.ItemGroup>
+          <Menu.ItemGroupLabel className="px-2 py-1.5 text-xs text-muted-foreground">Invoice INV-2041</Menu.ItemGroupLabel>
+          <Menu.Item id="download" value="download" className="rounded-sm px-2 py-1.5 text-sm">
+            Download PDF
+          </Menu.Item>
+          <Menu.Item id="duplicate" value="duplicate" className="rounded-sm px-2 py-1.5 text-sm">
+            Duplicate
+          </Menu.Item>
+          <Menu.Item id="remind" value="remind" className="rounded-sm px-2 py-1.5 text-sm">
+            Send reminder
+          </Menu.Item>
+        </Menu.ItemGroup>
+        <Menu.Separator className="my-1 h-px bg-border" />
+        <Menu.Item id="void" value="void" className="rounded-sm px-2 py-1.5 text-sm text-destructive">
+          Void invoice
+        </Menu.Item>
+      </Menu.Content>
+    </Menu.Positioner>
+  </Menu.Root>
+);
+
+const NavigationMenuStory: StoryRender = ({ sample }) => (
+  <NavigationMenu.Root defaultValue="product" className="relative">
+    <NavigationMenu.List className="flex items-center gap-1 rounded-md border border-border p-1">
+      <NavigationMenu.Item value="product" className="relative">
+        <NavigationMenu.Trigger className="rounded-sm px-3 py-1.5 text-sm data-[state=open]:bg-secondary">
+          {sample.item1 ?? "Product"}
+        </NavigationMenu.Trigger>
+        <NavigationMenu.Content value="product" className="absolute left-0 top-full z-10 mt-2 w-64 rounded-md border border-border bg-popover p-3 text-popover-foreground">
+          <NavigationMenu.Link value="product" href="#analytics" className="block rounded-sm px-2 py-1.5 text-sm">
+            Analytics
+          </NavigationMenu.Link>
+          <NavigationMenu.Link value="product" href="#automations" className="block rounded-sm px-2 py-1.5 text-sm">
+            Automations
+          </NavigationMenu.Link>
+          <NavigationMenu.Link value="product" href="#reports" className="block rounded-sm px-2 py-1.5 text-sm">
+            Reports
+          </NavigationMenu.Link>
+        </NavigationMenu.Content>
+      </NavigationMenu.Item>
+      <NavigationMenu.Item value="docs">
+        <NavigationMenu.Trigger className="rounded-sm px-3 py-1.5 text-sm data-[state=open]:bg-secondary">Docs</NavigationMenu.Trigger>
+      </NavigationMenu.Item>
+      <NavigationMenu.Item value="pricing">
+        <NavigationMenu.Trigger className="rounded-sm px-3 py-1.5 text-sm data-[state=open]:bg-secondary">Pricing</NavigationMenu.Trigger>
+      </NavigationMenu.Item>
+    </NavigationMenu.List>
+  </NavigationMenu.Root>
+);
+
+const NumberInputStory: StoryRender = ({ sample }) => (
+  <NumberInput.Root defaultValue="12" min={1} max={50} className="grid w-full max-w-xs gap-2">
+    <NumberInput.Label className="text-sm font-medium">{sample.label ?? "Seats"}</NumberInput.Label>
+    <div className="flex items-stretch gap-2">
+      <NumberInput.Scrubber aria-label="Drag to change seat count" className="w-1 rounded bg-border" />
+      <NumberInput.Control className="flex flex-1 items-center rounded-md border border-border bg-background">
+        <NumberInput.Input className="w-full bg-transparent px-3 py-2 text-sm outline-none" />
+        <NumberInput.IncrementTrigger aria-label="Add a seat" className="px-2 py-1 text-muted-foreground">
+          <ChevronUp className="size-4" />
+        </NumberInput.IncrementTrigger>
+        <NumberInput.DecrementTrigger aria-label="Remove a seat" className="px-2 py-1 text-muted-foreground">
+          <ChevronDown className="size-4" />
+        </NumberInput.DecrementTrigger>
+      </NumberInput.Control>
+    </div>
+  </NumberInput.Root>
+);
+
+const PaginationStory: StoryRender = ({ sample }) => (
+  <div className="w-full max-w-md">
+    <p className="mb-3 text-sm text-muted-foreground">Showing 31–40 of {sample.total ?? "214"} invoices</p>
+    <Pagination.Root count={214} pageSize={10} defaultPage={4} className="flex items-center gap-1">
+      <Pagination.PrevTrigger className="flex items-center gap-1 rounded-md border border-border px-3 py-1.5 text-sm">
+        <ChevronLeft className="size-4" />
+        Previous
+      </Pagination.PrevTrigger>
+      <Pagination.Context>
+        {(context) =>
+          context.pages.map((page, index) =>
+            page.type === "page" ? (
+              <Pagination.Item
+                key={index}
+                type="page"
+                value={page.value}
+                className="rounded-md px-3 py-1.5 text-sm data-[selected]:bg-secondary"
+              >
+                {page.value}
+              </Pagination.Item>
+            ) : (
+              <Pagination.Ellipsis key={index} index={index} className="px-2 text-sm text-muted-foreground">
+                …
+              </Pagination.Ellipsis>
+            ),
+          )
+        }
+      </Pagination.Context>
+      <Pagination.NextTrigger className="flex items-center gap-1 rounded-md border border-border px-3 py-1.5 text-sm">
+        Next
+        <ChevronRight className="size-4" />
+      </Pagination.NextTrigger>
+    </Pagination.Root>
+  </div>
+);
+
+export const storyRenders: Record<string, StoryRender> = {
+  "ark-ui/Accordion": AccordionStory,
+  "ark-ui/Angle Slider": AngleSliderStory,
+  "ark-ui/Avatar": AvatarStory,
+  "ark-ui/Carousel": CarouselStory,
+  "ark-ui/Checkbox": CheckboxStory,
+  "ark-ui/Clipboard": ClipboardStory,
+  "ark-ui/Collapsible": CollapsibleStory,
+  "ark-ui/Color Picker": ColorPickerStory,
+  "ark-ui/Combobox": ComboboxStory,
+  "ark-ui/Date Input": DateInputStory,
+  "ark-ui/Date Picker": DatePickerStory,
+  "ark-ui/Dialog": DialogStory,
+  "ark-ui/Drawer": DrawerStory,
+  "ark-ui/Editable": EditableStory,
+  "ark-ui/Field": FieldStory,
+  "ark-ui/Fieldset": FieldsetStory,
+  "ark-ui/File Upload": FileUploadStory,
+  "ark-ui/Floating Panel": FloatingPanelStory,
+  "ark-ui/Image Cropper": ImageCropperStory,
+  "ark-ui/Hover Card": HoverCardStory,
+  "ark-ui/Listbox": ListboxStory,
+  "ark-ui/Marquee": MarqueeStory,
+  "ark-ui/Menu": MenuStory,
+  "ark-ui/Navigation Menu": NavigationMenuStory,
+  "ark-ui/Number Input": NumberInputStory,
+  "ark-ui/Pagination": PaginationStory,
+};
```

## components/preview-stories/ark-ui-2.tsx

Status: added; +820; −0

```diff
@@ -1,0 +1,820 @@
+import * as React from "react";
+import { PasswordInput } from "@ark-ui/react/password-input";
+import { PinInput } from "@ark-ui/react/pin-input";
+import { Popover } from "@ark-ui/react/popover";
+import { Progress } from "@ark-ui/react/progress";
+import { QrCode } from "@ark-ui/react/qr-code";
+import { RadioGroup } from "@ark-ui/react/radio-group";
+import { RatingGroup } from "@ark-ui/react/rating-group";
+import { ScrollArea } from "@ark-ui/react/scroll-area";
+import { SegmentGroup } from "@ark-ui/react/segment-group";
+import { Select, createListCollection } from "@ark-ui/react/select";
+import { SignaturePad } from "@ark-ui/react/signature-pad";
+import { Slider } from "@ark-ui/react/slider";
+import { Splitter } from "@ark-ui/react/splitter";
+import { Steps } from "@ark-ui/react/steps";
+import { Switch } from "@ark-ui/react/switch";
+import { Tabs } from "@ark-ui/react/tabs";
+import { TagsInput } from "@ark-ui/react/tags-input";
+import { Timer } from "@ark-ui/react/timer";
+import { Toc } from "@ark-ui/react/toc";
+import { Toast, Toaster, createToaster } from "@ark-ui/react/toast";
+import type { ToastOptions } from "@ark-ui/react/toast";
+import { Toggle } from "@ark-ui/react/toggle";
+import { ToggleGroup } from "@ark-ui/react/toggle-group";
+import { Tooltip } from "@ark-ui/react/tooltip";
+import { Tour, useTour, type TourStepDetails } from "@ark-ui/react/tour";
+import { TreeView, createTreeCollection } from "@ark-ui/react/tree-view";
+import { Check, ChevronDown, ChevronRight, Eye, EyeOff, Star } from "lucide-react";
+import type { CSSProperties, ReactElement } from "react";
+import type { StoryRender } from "./type";
+
+/**
+ * Preview stories for Ark UI: each documented component rendered with its real
+ * package API — behaviour from `@ark-ui/react`, Col's theme tokens for the
+ * look. Overlay components are forced open so a screenshot shows their
+ * surface, and sample strings replace the display text a caller would relabel.
+ */
+
+const PasswordInputStory: StoryRender = ({ sample }) => (
+  <PasswordInput.Root className="w-full max-w-sm">
+    <PasswordInput.Label className="text-sm font-medium">{sample.label ?? "Password"}</PasswordInput.Label>
+    <PasswordInput.Control className="mt-2 flex items-center gap-2 rounded-md border border-border bg-background px-3 py-2">
+      <PasswordInput.Input
+        defaultValue="harbour-lantern-42"
+        className="w-full bg-transparent text-sm text-foreground outline-none"
+      />
+      <PasswordInput.VisibilityTrigger className="text-muted-foreground">
+        <PasswordInput.Indicator fallback={<EyeOff className="size-4" />}>
+          <Eye className="size-4" />
+        </PasswordInput.Indicator>
+      </PasswordInput.VisibilityTrigger>
+    </PasswordInput.Control>
+    <p className="mt-2 text-sm text-muted-foreground">
+      {sample.hint ?? "At least 12 characters. Never reused from another account."}
+    </p>
+  </PasswordInput.Root>
+);
+
+const PinInputStory: StoryRender = ({ sample }) => (
+  <PinInput.Root count={6} defaultValue={["8", "3", "1", "", "", ""]} className="w-full max-w-sm">
+    <PinInput.Label className="text-sm font-medium">{sample.label ?? "Verification code"}</PinInput.Label>
+    <PinInput.Control className="mt-2 flex gap-2">
+      {[0, 1, 2, 3, 4, 5].map((index) => (
+        <PinInput.Input
+          key={index}
+          index={index}
+          className="size-11 rounded-md border border-border bg-background text-center text-lg text-foreground"
+        />
+      ))}
+    </PinInput.Control>
+    <PinInput.HiddenInput />
+    <p className="mt-2 text-sm text-muted-foreground">{sample.hint ?? "Sent to samira@northwind.dev"}</p>
+  </PinInput.Root>
+);
+
+const PopoverStory: StoryRender = ({ sample }) => (
+  <Popover.Root open onOpenChange={() => {}}>
+    <Popover.Trigger className="rounded-md border border-border px-4 py-2 text-sm">
+      {sample.triggerLabel ?? "Share report"}
+    </Popover.Trigger>
+    <Popover.Positioner>
+      <Popover.Content className="w-72 rounded-xl border border-border bg-popover p-4 text-popover-foreground">
+        <Popover.Title className="text-sm font-semibold">Share Q3 revenue</Popover.Title>
+        <Popover.Description className="mt-1 text-sm text-muted-foreground">
+          Anyone at Northwind with the link can view this report.
+        </Popover.Description>
+        <input
+          readOnly
+          defaultValue="col.dev/reports/q3-revenue"
+          className="mt-3 w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
+        />
+        <div className="mt-3 flex items-center justify-between gap-3">
+          <Popover.CloseTrigger className="rounded-md border border-border px-3 py-1.5 text-sm">Done</Popover.CloseTrigger>
+          <span className="text-sm text-muted-foreground">Expires in 7 days</span>
+        </div>
+      </Popover.Content>
+    </Popover.Positioner>
+  </Popover.Root>
+);
+
+const ProgressCircularStory: StoryRender = ({ sample }) => (
+  <Progress.Root value={68} className="flex items-center gap-5">
+    <Progress.Circle
+      className="shrink-0 text-foreground"
+      style={{ "--size": "96px", "--thickness": "8px" } as CSSProperties}
+    >
+      <Progress.CircleTrack className="stroke-border" />
+      <Progress.CircleRange className="stroke-primary" />
+    </Progress.Circle>
+    <div>
+      <Progress.Label className="text-sm font-medium">{sample.label ?? "Storage used"}</Progress.Label>
+      <Progress.ValueText className="mt-1 block text-2xl font-semibold" />
+      <p className="text-sm text-muted-foreground">136 GB of 200 GB across 4 projects</p>
+    </div>
+  </Progress.Root>
+);
+
+const ProgressLinearStory: StoryRender = ({ sample }) => (
+  <Progress.Root value={72} className="w-full max-w-md">
+    <div className="flex items-baseline justify-between text-sm">
+      <Progress.Label className="font-medium">{sample.label ?? "Deploying checkout-service"}</Progress.Label>
+      <Progress.ValueText className="text-muted-foreground" />
+    </div>
+    <Progress.Track className="mt-2 h-2 w-full overflow-hidden rounded-full bg-muted">
+      <Progress.Range className="h-full rounded-full bg-primary" />
+    </Progress.Track>
+    <p className="mt-2 text-sm text-muted-foreground">Running smoke tests against production, step 9 of 13.</p>
+  </Progress.Root>
+);
+
+const QrCodeStory: StoryRender = ({ sample }) => (
+  <div className="flex items-center gap-5">
+    <QrCode.Root value="https://col.dev/checkin/devops-summit-2026" className="rounded-xl bg-primary-foreground p-3">
+      <QrCode.Frame className="size-36">
+        <QrCode.Pattern className="fill-background" />
+      </QrCode.Frame>
+    </QrCode.Root>
+    <div>
+      <p className="text-sm font-medium">{sample.title ?? "DevOps Summit check-in"}</p>
+      <p className="mt-1 text-sm text-muted-foreground">Scan at the front desk to collect your badge.</p>
+    </div>
+  </div>
+);
+
+const RadioGroupStory: StoryRender = ({ sample }) => (
+  <RadioGroup.Root defaultValue="annual" className="grid w-full max-w-sm gap-3">
+    <RadioGroup.Label className="text-sm font-medium">{sample.label ?? "Billing cycle"}</RadioGroup.Label>
+    {[
+      ["monthly", "Monthly — $12 per seat"],
+      ["annual", "Annual — $120 per seat"],
+      ["usage", "Usage-based — $0.04 per build minute"],
+    ].map(([value, label]) => (
+      <RadioGroup.Item key={value} value={value} className="flex items-center gap-3 text-sm">
+        <RadioGroup.ItemControl className="flex size-5 items-center justify-center rounded-full border border-border data-[state=checked]:border-primary data-[state=checked]:bg-primary" />
+        <RadioGroup.ItemText>{label}</RadioGroup.ItemText>
+        <RadioGroup.ItemHiddenInput />
+      </RadioGroup.Item>
+    ))}
+  </RadioGroup.Root>
+);
+
+const RatingGroupStory: StoryRender = ({ sample }) => (
+  <RatingGroup.Root count={5} defaultValue={4} className="w-full max-w-sm">
+    <RatingGroup.Label className="text-sm font-medium">
+      {sample.label ?? "How was your support experience?"}
+    </RatingGroup.Label>
+    <RatingGroup.Control className="mt-2 flex gap-1">
+      {[1, 2, 3, 4, 5].map((index) => (
+        <RatingGroup.Item key={index} index={index} className="text-muted-foreground data-[highlighted]:text-primary">
+          <Star className="size-7 fill-current" />
+        </RatingGroup.Item>
+      ))}
+    </RatingGroup.Control>
+    <RatingGroup.HiddenInput />
+    <p className="mt-2 text-sm text-muted-foreground">
+      {sample.caption ?? "Ticket #3182 — refund question, resolved in 2 hours"}
+    </p>
+  </RatingGroup.Root>
+);
+
+const ScrollAreaStory: StoryRender = ({ sample }) => (
+  <ScrollArea.Root className="h-56 w-full max-w-md rounded-xl border border-border">
+    <ScrollArea.Viewport className="h-full">
+      <ScrollArea.Content className="p-4">
+        <div className="grid gap-2 font-mono text-xs text-muted-foreground">
+          {[
+            "$ npm run build --filter=checkout-service",
+            "▸ compiling 214 modules with esbuild",
+            "▸ type-checking src/ ... ok",
+            "▸ emitting dist/ (1.8 MB)",
+            "▸ running migrations against staging",
+            "  - 20261009_add_invoice_index.sql ... applied",
+            "  - 20261009_backfill_tax_rates.sql ... applied",
+            "▸ uploading artifacts to the release bucket",
+            "▸ warming the edge cache in 12 regions",
+            "▸ health checks: api 200, billing 200, webhooks 200",
+            "build finished in 47s — ready for review",
+          ].map((line) => (
+            <p key={line}>{line}</p>
+          ))}
+        </div>
+      </ScrollArea.Content>
+    </ScrollArea.Viewport>
+    <ScrollArea.Scrollbar orientation="vertical" className="flex w-2 p-0.5">
+      <ScrollArea.Thumb className="flex-1 rounded-full bg-border" />
+    </ScrollArea.Scrollbar>
+    <ScrollArea.Corner />
+  </ScrollArea.Root>
+);
+
+const SegmentGroupStory: StoryRender = ({ sample }) => (
+  <SegmentGroup.Root defaultValue="7d" className="w-full max-w-md">
+    <SegmentGroup.Label className="sr-only">{sample.label ?? "Time range"}</SegmentGroup.Label>
+    <div className="relative flex rounded-lg border border-border p-1 text-sm">
+      <SegmentGroup.Indicator
+        className="rounded-md bg-secondary"
+        style={
+          {
+            "--transition-property": "left, top, width, height",
+            top: "var(--top)",
+            width: "var(--width)",
+            height: "var(--height)",
+          } as CSSProperties
+        }
+      />
+      {[
+        ["24h", "Last 24 hours"],
+        ["7d", "Last 7 days"],
+        ["30d", "Last 30 days"],
+        ["qtd", "Quarter to date"],
+      ].map(([value, label]) => (
+        <SegmentGroup.Item
+          key={value}
+          value={value}
+          className="relative z-10 flex-1 rounded-md px-3 py-1.5 text-center text-muted-foreground data-[state=checked]:text-foreground"
+        >
+          <SegmentGroup.ItemText>{label}</SegmentGroup.ItemText>
+          <SegmentGroup.ItemControl className="absolute inset-0 rounded-md" />
+          <SegmentGroup.ItemHiddenInput />
+        </SegmentGroup.Item>
+      ))}
+    </div>
+    <p className="mt-2 text-sm text-muted-foreground">{sample.caption ?? "Revenue and deploy counts, refreshed hourly"}</p>
+  </SegmentGroup.Root>
+);
+
+interface RoleOption {
+  label: string;
+  value: string;
+}
+
+const roleOptions: RoleOption[] = [
+  { value: "viewer", label: "Viewer" },
+  { value: "editor", label: "Editor" },
+  { value: "admin", label: "Admin" },
+  { value: "billing", label: "Billing manager" },
+];
+
+const roleCollection = createListCollection<RoleOption>({
+  items: roleOptions,
+  groupBy: (item) => (item.value === "billing" ? "restricted" : "workspace"),
+});
+
+const SelectStory: StoryRender = ({ sample }) => (
+  <Select.Root collection={roleCollection} defaultValue={["editor"]} open onOpenChange={() => {}}>
+    <Select.Label className="text-sm font-medium">{sample.label ?? "Workspace role"}</Select.Label>
+    <Select.Control className="mt-2 flex w-64 items-center justify-between rounded-md border border-border bg-background px-3 py-2 text-sm">
+      <Select.Trigger className="flex w-full items-center justify-between">
+        <Select.ValueText />
+        <Select.Indicator>
+          <ChevronDown className="size-4 text-muted-foreground" />
+        </Select.Indicator>
+      </Select.Trigger>
+      <Select.HiddenSelect />
+    </Select.Control>
+    <Select.Positioner>
+      <Select.Content className="rounded-md border border-border bg-popover p-1 text-popover-foreground">
+        {["workspace", "restricted"].map((groupId) => (
+          <Select.ItemGroup key={groupId} id={groupId}>
+            <Select.ItemGroupLabel className="px-2 py-1.5 text-xs text-muted-foreground">
+              {groupId === "workspace" ? "Workspace roles" : "Restricted"}
+            </Select.ItemGroupLabel>
+            {roleCollection.items
+              .filter((item) => (groupId === "restricted" ? item.value === "billing" : item.value !== "billing"))
+              .map((item) => (
+                <Select.Item key={item.value} item={item} className="flex items-center justify-between rounded-sm px-2 py-1.5 text-sm">
+                  <Select.ItemText>{item.label}</Select.ItemText>
+                  <Select.ItemIndicator>
+                    <Check className="size-4" />
+                  </Select.ItemIndicator>
+                </Select.Item>
+              ))}
+          </Select.ItemGroup>
+        ))}
+      </Select.Content>
+    </Select.Positioner>
+  </Select.Root>
+);
+
+// Perfect-freehand strokes are filled outlines, so the seeded signature is a
+// thin ribbon traced out and back rather than a single stroked curve.
+const seededSignature =
+  "M 12 78 C 40 14, 78 12, 98 58 C 118 104, 152 108, 182 46 C 194 22, 218 30, 228 56 C 219 41, 202 34, 192 56 C 163 116, 121 112, 101 66 C 81 20, 42 22, 17 84 Z";
+
+const SignaturePadStory: StoryRender = ({ sample }) => (
+  <SignaturePad.Root
+    defaultPaths={[seededSignature]}
+    drawing={{ fill: "currentColor" }}
+    className="w-full max-w-md text-foreground"
+  >
+    <SignaturePad.Label className="text-sm font-medium">
+      {sample.label ?? "Sign the statement of work"}
+    </SignaturePad.Label>
+    <SignaturePad.Control className="relative mt-2 h-36 rounded-xl border border-border bg-background">
+      <SignaturePad.Segment className="text-foreground" />
+      <SignaturePad.Guide className="absolute inset-x-8 bottom-8 border-b border-dashed border-border" />
+    </SignaturePad.Control>
+    <div className="mt-3 flex items-center justify-between">
+      <SignaturePad.ClearTrigger className="rounded-md border border-border px-3 py-1.5 text-sm">
+        Clear
+      </SignaturePad.ClearTrigger>
+      <span className="text-sm text-muted-foreground">Signed by Priya Raman on Oct 9</span>
+    </div>
+  </SignaturePad.Root>
+);
+
+const SliderStory: StoryRender = ({ sample }) => (
+  <Slider.Root defaultValue={[48]} min={10} max={200} className="w-full max-w-md">
+    <div className="flex items-baseline justify-between text-sm">
+      <Slider.Label className="font-medium">{sample.label ?? "Daily ad budget"}</Slider.Label>
+      <Slider.ValueText className="text-muted-foreground" />
+    </div>
+    <Slider.Control className="mt-4">
+      <Slider.Track className="h-2 rounded-full bg-muted">
+        <Slider.Range className="rounded-full bg-primary" />
+      </Slider.Track>
+      <Slider.Thumb index={0} className="block size-5 rounded-full border-2 border-primary bg-background">
+        <Slider.HiddenInput />
+      </Slider.Thumb>
+      <Slider.MarkerGroup className="relative mt-3 h-4 text-xs text-muted-foreground">
+        <Slider.Marker value={10} className="absolute -translate-x-1/2">
+          $10
+        </Slider.Marker>
+        <Slider.Marker value={105} className="absolute -translate-x-1/2">
+          $105
+        </Slider.Marker>
+        <Slider.Marker value={200} className="absolute -translate-x-1/2">
+          $200
+        </Slider.Marker>
+      </Slider.MarkerGroup>
+    </Slider.Control>
+    <p className="mt-2 text-sm text-muted-foreground">Capped at $1,440 spend per week.</p>
+  </Slider.Root>
+);
+
+const SplitterStory: StoryRender = ({ sample }) => (
+  <Splitter.Root
+    panels={[{ id: "files" }, { id: "diff" }]}
+    defaultSize={[200, 380]}
+    className="flex h-64 w-full max-w-2xl overflow-hidden rounded-xl border border-border text-sm"
+  >
+    <Splitter.Panel id="files" className="min-w-0 p-4">
+      <p className="text-xs uppercase tracking-wide text-muted-foreground">{sample.sidebarTitle ?? "Changed files"}</p>
+      <ul className="mt-2 grid gap-1.5 text-muted-foreground">
+        <li className="text-foreground">src/billing/Invoice.tsx</li>
+        <li>src/billing/tax.ts</li>
+        <li>src/api/stripe-webhook.ts</li>
+        <li>tests/billing/invoice.test.ts</li>
+      </ul>
+    </Splitter.Panel>
+    <Splitter.ResizeTrigger id="files:diff" className="flex w-px items-center justify-center bg-border">
+      <Splitter.ResizeTriggerIndicator className="h-10 w-1 rounded-full bg-border" />
+    </Splitter.ResizeTrigger>
+    <Splitter.Panel id="diff" className="min-w-0 flex-1 p-4">
+      <p className="text-xs uppercase tracking-wide text-muted-foreground">
+        {sample.mainTitle ?? "src/billing/tax.ts"}
+      </p>
+      <div className="mt-2 grid gap-1.5 font-mono text-xs">
+        <p className="text-muted-foreground">export function taxFor(region: Region) {"{"}</p>
+        <p className="text-destructive">-  return rates[region] ?? 0</p>
+        <p className="text-primary-foreground">+  return rates[region] ?? defaultRate</p>
+        <p className="text-muted-foreground">{"}"}</p>
+      </div>
+    </Splitter.Panel>
+  </Splitter.Root>
+);
+
+const StepsStory: StoryRender = ({ sample }) => (
+  <Steps.Root count={3} defaultStep={1} className="w-full max-w-xl">
+    <Steps.List>
+      {[
+        sample.step1 ?? "Create your account",
+        sample.step2 ?? "Set up the workspace",
+        sample.step3 ?? "Invite your team",
+      ].map((label, index) => (
+        <Steps.Item key={label} index={index} className="flex items-center gap-2 last:flex-none">
+          <Steps.Trigger className="flex items-center gap-2 text-sm text-muted-foreground data-[complete]:text-foreground data-[current]:text-foreground">
+            <Steps.Indicator className="flex size-7 items-center justify-center rounded-full border border-border text-xs data-[complete]:border-primary data-[complete]:bg-primary">
+              {index + 1}
+            </Steps.Indicator>
+            {label}
+          </Steps.Trigger>
+          <Steps.Separator className="h-px min-w-6 flex-1 bg-border" />
+        </Steps.Item>
+      ))}
+    </Steps.List>
+    <Steps.Content index={0} className="mt-4 rounded-xl border border-border p-4 text-sm">
+      Your Northwind account is verified. Pick a workspace URL to continue.
+    </Steps.Content>
+    <Steps.Content index={1} className="mt-4 rounded-xl border border-border p-4 text-sm">
+      Workspace &quot;northwind-analytics&quot; is ready. Choose which projects to import from GitHub.
+    </Steps.Content>
+    <Steps.Content index={2} className="mt-4 rounded-xl border border-border p-4 text-sm">
+      Send invites to teammates — they join with view access until you promote them.
+    </Steps.Content>
+    <Steps.CompletedContent className="mt-4 rounded-xl border border-border p-4 text-sm">
+      Setup complete. Your first deploy will appear on the dashboard in about a minute.
+    </Steps.CompletedContent>
+    <div className="mt-4 flex items-center gap-3">
+      <Steps.PrevTrigger className="rounded-md border border-border px-4 py-2 text-sm">Back</Steps.PrevTrigger>
+      <Steps.NextTrigger className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground">
+        Continue
+      </Steps.NextTrigger>
+      <Steps.Context>
+        {(steps) => (
+          <Steps.Progress className="ml-auto text-sm text-muted-foreground">
+            Step {steps.value + 1} of {steps.count}
+          </Steps.Progress>
+        )}
+      </Steps.Context>
+    </div>
+  </Steps.Root>
+);
+
+const SwitchStory: StoryRender = ({ sample }) => (
+  <div className="flex w-full max-w-md items-center justify-between gap-6 rounded-xl border border-border p-4">
+    <Switch.Root defaultChecked className="flex items-center gap-3">
+      <Switch.Control className="flex h-6 w-11 items-center rounded-full bg-muted p-0.5 data-[state=checked]:bg-primary">
+        <Switch.Thumb className="size-5 rounded-full bg-primary-foreground data-[state=checked]:translate-x-5" />
+      </Switch.Control>
+      <Switch.Label className="text-sm font-medium">{sample.label ?? "Deploy alerts"}</Switch.Label>
+      <Switch.HiddenInput />
+    </Switch.Root>
+    <p className="text-sm text-muted-foreground">Email me when a production deploy fails.</p>
+  </div>
+);
+
+const TabsStory: StoryRender = ({ sample }) => (
+  <Tabs.Root defaultValue="deploys" className="w-full max-w-xl">
+    <Tabs.List className="flex gap-2 border-b border-border">
+      {[
+        ["overview", sample.tab1 ?? "Overview"],
+        ["deploys", sample.tab2 ?? "Deploys"],
+        ["settings", sample.tab3 ?? "Settings"],
+      ].map(([value, label]) => (
+        <Tabs.Trigger
+          key={value}
+          value={value}
+          className="border-b-2 border-transparent px-3 py-2 text-sm text-muted-foreground data-[selected]:border-primary data-[selected]:text-foreground"
+        >
+          {label}
+        </Tabs.Trigger>
+      ))}
+    </Tabs.List>
+    <Tabs.Content value="overview" className="pt-4 text-sm text-muted-foreground">
+      Traffic grew 18% this week, led by the pricing page. 4,210 sessions across 2,900 visitors.
+    </Tabs.Content>
+    <Tabs.Content value="deploys" className="grid gap-2 pt-4 text-sm">
+      {[
+        ["checkout-service", "v2.4.1", "12 min ago", "Passed"],
+        ["web-dashboard", "v9.18.0", "2 hours ago", "Passed"],
+        ["billing-worker", "v1.7.3", "Yesterday", "Rolled back"],
+      ].map(([service, version, when, status]) => (
+        <div key={service} className="flex items-center justify-between gap-4">
+          <span>
+            {service} {version}
+          </span>
+          <span className="text-muted-foreground">{when}</span>
+          <span className={status === "Passed" ? "text-primary-foreground" : "text-destructive"}>{status}</span>
+        </div>
+      ))}
+    </Tabs.Content>
+    <Tabs.Content value="settings" className="pt-4 text-sm text-muted-foreground">
+      Deploy previews run for every pull request; production deploys require one approval.
+    </Tabs.Content>
+  </Tabs.Root>
+);
+
+const TagsInputStory: StoryRender = ({ sample }) => (
+  <TagsInput.Root defaultValue={["changelog", "performance", "billing"]} className="w-full max-w-md">
+    <TagsInput.Label className="text-sm font-medium">{sample.label ?? "Release tags"}</TagsInput.Label>
+    <TagsInput.Control className="mt-2 flex flex-wrap items-center gap-2 rounded-md border border-border bg-background px-2 py-2">
+      <TagsInput.Context>
+        {(tagsInput) =>
+          tagsInput.value.map((tag) => (
+            <TagsInput.Item
+              key={tag}
+              index={tag}
+              value={tag}
+              className="flex items-center gap-1 rounded-md border border-border px-2 py-1 text-sm"
+            >
+              <TagsInput.ItemPreview>
+                <TagsInput.ItemText>{tag}</TagsInput.ItemText>
+                <TagsInput.ItemDeleteTrigger aria-label={`Remove ${tag}`} className="pl-1 text-muted-foreground">
+                  ✕
+                </TagsInput.ItemDeleteTrigger>
+              </TagsInput.ItemPreview>
+              <TagsInput.ItemInput />
+            </TagsInput.Item>
+          ))
+        }
+      </TagsInput.Context>
+      <TagsInput.Input placeholder="Add a tag…" className="min-w-24 flex-1 bg-transparent px-1 text-sm outline-none" />
+    </TagsInput.Control>
+    <div className="mt-2 flex items-center justify-between">
+      <span className="text-sm text-muted-foreground">Shown on the changelog entry.</span>
+      <TagsInput.ClearTrigger className="text-sm text-muted-foreground">Clear all</TagsInput.ClearTrigger>
+    </div>
+    <TagsInput.HiddenInput />
+  </TagsInput.Root>
+);
+
+const TimerStory: StoryRender = ({ sample }) => (
+  <Timer.Root countdown autoStart={false} startMs={25 * 60 * 1000} className="w-full max-w-md">
+    <p className="text-sm text-muted-foreground">{sample.label ?? "Focus session"}</p>
+    <Timer.Area className="mt-2 flex items-baseline gap-1">
+      <Timer.Item type="minutes" className="text-4xl font-semibold" />
+      <Timer.Separator className="text-4xl text-muted-foreground">:</Timer.Separator>
+      <Timer.Item type="seconds" className="text-4xl font-semibold" />
+    </Timer.Area>
+    <Timer.Control className="mt-4 flex gap-2">
+      <Timer.ActionTrigger
+        action="start"
+        className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground"
+      >
+        Start
+      </Timer.ActionTrigger>
+      <Timer.ActionTrigger action="pause" className="rounded-md border border-border px-4 py-2 text-sm">
+        Pause
+      </Timer.ActionTrigger>
+      <Timer.ActionTrigger action="reset" className="rounded-md border border-border px-4 py-2 text-sm">
+        Reset
+      </Timer.ActionTrigger>
+    </Timer.Control>
+    <p className="mt-3 text-sm text-muted-foreground">Blocks notifications until the session ends.</p>
+  </Timer.Root>
+);
+
+interface DocHeading {
+  value: string;
+  depth: number;
+  label: string;
+}
+
+const docHeadings: DocHeading[] = [
+  { value: "why-col", depth: 2, label: "Why Col" },
+  { value: "install", depth: 2, label: "Installation" },
+  { value: "connect-api", depth: 3, label: "Connect your API" },
+  { value: "first-preview", depth: 2, label: "Your first preview" },
+  { value: "deploy", depth: 2, label: "Deploying" },
+];
+
+const TocStory: StoryRender = ({ sample }) => (
+  <Toc.Root items={docHeadings} activeIds={["connect-api"]} className="w-full max-w-sm">
+    <Toc.Nav placement="right" className="rounded-xl border border-border p-4">
+      <Toc.Title className="text-sm font-medium">{sample.title ?? "On this page"}</Toc.Title>
+      <Toc.List className="mt-3 grid gap-1">
+        {docHeadings.map((heading) => (
+          <Toc.Item key={heading.value} item={heading}>
+            <Toc.Link
+              href={`#${heading.value}`}
+              className={`block py-1 text-sm text-muted-foreground data-[active]:text-foreground ${
+                heading.depth > 2 ? "pl-6" : "pl-3"
+              }`}
+            >
+              {heading.label}
+            </Toc.Link>
+          </Toc.Item>
+        ))}
+      </Toc.List>
+    </Toc.Nav>
+  </Toc.Root>
+);
+
+const deploymentToaster = createToaster({
+  placement: "bottom-end",
+  duration: 600000,
+  removeDelay: 0,
+});
+
+deploymentToaster.create({
+  id: "deployment-complete",
+  type: "success",
+  title: "Deployment complete",
+  description: "checkout-service v2.4.1 is live in production.",
+  action: { label: "View logs", onClick: () => {} },
+  closable: true,
+});
+
+const ToastStory: StoryRender = ({ sample }) => (
+  <div className="w-80">
+    <Toaster toaster={deploymentToaster}>
+      {(toast: ToastOptions) => (
+        <Toast.Root className="rounded-xl border border-border bg-popover p-4 text-popover-foreground">
+          <Toast.Title className="text-sm font-semibold">{sample.title ?? toast.title}</Toast.Title>
+          <Toast.Description className="mt-1 text-sm text-muted-foreground">{toast.description}</Toast.Description>
+          <div className="mt-3 flex items-center gap-2">
+            <Toast.ActionTrigger className="rounded-md border border-border px-3 py-1.5 text-sm">
+              {toast.action?.label}
+            </Toast.ActionTrigger>
+            <Toast.CloseTrigger aria-label="Dismiss" className="ml-auto text-muted-foreground">
+              ✕
+            </Toast.CloseTrigger>
+          </div>
+        </Toast.Root>
+      )}
+    </Toaster>
+  </div>
+);
+
+const ToggleStory: StoryRender = ({ sample }) => (
+  <Toggle.Root defaultPressed className="rounded-md border border-border px-4 py-2 text-sm data-[state=on]:bg-secondary">
+    <Toggle.Indicator fallback={sample.offLabel ?? "Pin toolbar"}>{sample.label ?? "Pinned"}</Toggle.Indicator>
+  </Toggle.Root>
+);
+
+const ToggleGroupStory: StoryRender = ({ sample }) => (
+  <div>
+    <p className="text-sm font-medium">{sample.label ?? "Project view"}</p>
+    <ToggleGroup.Root defaultValue={["board"]} className="mt-2 flex rounded-md border border-border p-1">
+      {[
+        ["list", "List"],
+        ["board", "Board"],
+        ["calendar", "Calendar"],
+      ].map(([value, label]) => (
+        <ToggleGroup.Item
+          key={value}
+          value={value}
+          className="rounded-sm px-3 py-1.5 text-sm data-[state=on]:bg-secondary"
+        >
+          {label}
+        </ToggleGroup.Item>
+      ))}
+    </ToggleGroup.Root>
+  </div>
+);
+
+const TooltipStory: StoryRender = ({ sample }) => (
+  <Tooltip.Root open onOpenChange={() => {}}>
+    <Tooltip.Trigger className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground">
+      {sample.triggerLabel ?? "Deploy"}
+    </Tooltip.Trigger>
+    <Tooltip.Positioner>
+      <Tooltip.Content className="rounded-md border border-border bg-popover px-3 py-1.5 text-sm text-popover-foreground">
+        Deploys the main branch to production.
+      </Tooltip.Content>
+    </Tooltip.Positioner>
+  </Tooltip.Root>
+);
+
+const tourSteps: TourStepDetails[] = [
+  {
+    id: "welcome",
+    type: "dialog",
+    title: "Welcome to Northwind Analytics",
+    description: "Track revenue, deploys, and team activity in one dashboard. The tour takes about a minute.",
+    actions: [
+      { label: "Start tour", action: "next" },
+      { label: "Skip", action: "skip" },
+    ],
+  },
+  {
+    id: "done",
+    type: "dialog",
+    title: "You are all set",
+    description: "Invite teammates from Settings whenever you are ready — they start with view access.",
+    actions: [{ label: "Done", action: "dismiss" }],
+  },
+];
+
+function TourDemo({ title }: { title: string }): ReactElement {
+  const tour = useTour({ steps: tourSteps, stepId: "welcome" });
+  return (
+    <Tour.Root tour={tour}>
+      <Tour.Backdrop className="bg-black/60" />
+      <Tour.Spotlight />
+      <Tour.Positioner className="fixed inset-0 z-50 grid place-items-center">
+        <Tour.Content className="relative w-96 rounded-xl border border-border bg-card p-6 text-card-foreground">
+          <Tour.CloseTrigger aria-label="Close tour" className="absolute right-4 top-4 text-muted-foreground">
+            ✕
+          </Tour.CloseTrigger>
+          <Tour.Title className="text-base font-semibold">{title}</Tour.Title>
+          <Tour.Description className="mt-2 text-sm text-muted-foreground" />
+          <div className="mt-6 flex items-center justify-between gap-3">
+            <Tour.ProgressText className="text-sm text-muted-foreground" />
+            <Tour.Actions>
+              {(actions) => (
+                <div className="flex gap-2">
+                  {actions.map((action) => (
+                    <Tour.ActionTrigger
+                      key={action.label}
+                      action={action}
+                      className="rounded-md border border-border px-4 py-2 text-sm"
+                    >
+                      {action.label}
+                    </Tour.ActionTrigger>
+                  ))}
+                </div>
+              )}
+            </Tour.Actions>
+          </div>
+        </Tour.Content>
+      </Tour.Positioner>
+    </Tour.Root>
+  );
+}
+
+const TourStory: StoryRender = ({ sample }) => (
+  <TourDemo title={sample.title ?? "Welcome to Northwind Analytics"} />
+);
+
+interface ProjectNode {
+  value: string;
+  children?: ProjectNode[];
+}
+
+const projectTree: ProjectNode = {
+  value: "northwind-dashboard",
+  children: [
+    {
+      value: "src",
+      children: [
+        {
+          value: "components",
+          children: [{ value: "RevenueChart.tsx" }, { value: "DeployList.tsx" }],
+        },
+        { value: "app.tsx" },
+        { value: "styles.css" },
+      ],
+    },
+    { value: "package.json" },
+    { value: "README.md" },
+  ],
+};
+
+const projectFiles = createTreeCollection<ProjectNode>({ rootNode: projectTree });
+
+function ProjectNodeItem({ node, indexPath }: { node: ProjectNode; indexPath: number[] }): ReactElement {
+  return (
+    <TreeView.NodeProvider node={node} indexPath={indexPath}>
+      {node.children ? (
+        <TreeView.Branch>
+          <TreeView.BranchControl className="flex items-center gap-1.5 py-1 text-sm">
+            <TreeView.BranchIndicator className="flex items-center data-[state=open]:rotate-90">
+              <ChevronRight className="size-3.5" />
+            </TreeView.BranchIndicator>
+            <TreeView.BranchText>{node.value}</TreeView.BranchText>
+          </TreeView.BranchControl>
+          <TreeView.BranchContent className="ml-1.5 border-l border-border pl-2">
+            {node.children.map((child, index) => (
+              <ProjectNodeItem key={child.value} node={child} indexPath={[...indexPath, index]} />
+            ))}
+          </TreeView.BranchContent>
+        </TreeView.Branch>
+      ) : (
+        <TreeView.Item className="flex items-center gap-1.5 py-1 pl-5 text-sm text-muted-foreground data-[selected]:text-foreground">
+          <TreeView.ItemIndicator className="text-primary">
+            <Check className="size-3.5" />
+          </TreeView.ItemIndicator>
+          <TreeView.ItemText>{node.value}</TreeView.ItemText>
+        </TreeView.Item>
+      )}
+    </TreeView.NodeProvider>
+  );
+}
+
+const TreeViewStory: StoryRender = ({ sample }) => (
+  <TreeView.Root
+    collection={projectFiles}
+    defaultExpandedValue={["src", "components"]}
+    defaultSelectedValue={["app.tsx"]}
+    className="w-full max-w-xs"
+  >
+    <TreeView.Label className="text-sm font-medium">{sample.label ?? "Project files"}</TreeView.Label>
+    <TreeView.Tree className="mt-2">
+      {(projectTree.children ?? []).map((node, index) => (
+        <ProjectNodeItem key={node.value} node={node} indexPath={[index]} />
+      ))}
+    </TreeView.Tree>
+  </TreeView.Root>
+);
+
+export const storyRenders: Record<string, StoryRender> = {
+  "ark-ui/Password Input": PasswordInputStory,
+  "ark-ui/Pin Input": PinInputStory,
+  "ark-ui/Popover": PopoverStory,
+  "ark-ui/Progress - Circular": ProgressCircularStory,
+  "ark-ui/Progress - Linear": ProgressLinearStory,
+  "ark-ui/QR Code": QrCodeStory,
+  "ark-ui/Radio Group": RadioGroupStory,
+  "ark-ui/Rating Group": RatingGroupStory,
+  "ark-ui/Scroll Area": ScrollAreaStory,
+  "ark-ui/Segment Group": SegmentGroupStory,
+  "ark-ui/Select": SelectStory,
+  "ark-ui/Signature Pad": SignaturePadStory,
+  "ark-ui/Slider": SliderStory,
+  "ark-ui/Splitter": SplitterStory,
+  "ark-ui/Steps": StepsStory,
+  "ark-ui/Switch": SwitchStory,
+  "ark-ui/Tabs": TabsStory,
+  "ark-ui/Tags Input": TagsInputStory,
+  "ark-ui/Timer": TimerStory,
+  "ark-ui/Table of Contents": TocStory,
+  "ark-ui/Toast": ToastStory,
+  "ark-ui/Toggle": ToggleStory,
+  "ark-ui/Toggle Group": ToggleGroupStory,
+  "ark-ui/Tooltip": TooltipStory,
+  "ark-ui/Tour": TourStory,
+  "ark-ui/Tree View": TreeViewStory,
+};
```

## components/preview-stories/base-ui-1.tsx

Status: added; +528; −0

```diff
@@ -1,0 +1,528 @@
+import {
+  Accordion,
+  AlertDialog,
+  Autocomplete,
+  Avatar,
+  Button,
+  Checkbox,
+  CheckboxGroup,
+  Collapsible,
+  Combobox,
+  ContextMenu,
+  Dialog,
+  Drawer,
+  Field,
+  Fieldset,
+  Form,
+  Input,
+  Menu,
+  Menubar,
+  Meter,
+} from "@base-ui/react";
+import { Check, ChevronDown, Minus, X } from "lucide-react";
+import type { StoryRender } from "./type";
+
+/**
+ * Preview stories for Base UI: each documented component as the installed
+ * `@base-ui/react` package documents it — accessible behaviour from the real
+ * parts, Col's theme tokens for the look. Popups and dialogs hold themselves
+ * open (`open` plus a no-op `onOpenChange`) so a screenshot captures the
+ * interesting surface, not just the trigger.
+ */
+
+const AccordionStory: StoryRender = ({ sample }) => (
+  <Accordion.Root defaultValue={["billing"]} className="w-full max-w-xl">
+    <Accordion.Item value="billing" className="border-b border-border">
+      <Accordion.Header>
+        <Accordion.Trigger className="flex w-full items-center justify-between py-4 text-left text-sm font-medium">
+          {sample.question ?? "How does billing work?"}
+          <ChevronDown className="size-4 shrink-0 text-muted-foreground" />
+        </Accordion.Trigger>
+      </Accordion.Header>
+      <Accordion.Panel className="pb-4 text-sm text-muted-foreground">
+        Every plan is billed monthly per workspace. The Growth tier adds usage-based pricing above 50,000 tracked
+        invoices per month.
+      </Accordion.Panel>
+    </Accordion.Item>
+    <Accordion.Item value="stripe" className="border-b border-border">
+      <Accordion.Header>
+        <Accordion.Trigger className="flex w-full items-center justify-between py-4 text-left text-sm font-medium">
+          Can I import invoices from Stripe?
+          <ChevronDown className="size-4 shrink-0 text-muted-foreground" />
+        </Accordion.Trigger>
+      </Accordion.Header>
+      <Accordion.Panel className="pb-4 text-sm text-muted-foreground">
+        Yes. Connect a Stripe account and open and paid invoices sync into Ledger every hour.
+      </Accordion.Panel>
+    </Accordion.Item>
+    <Accordion.Item value="currencies" className="border-b border-border">
+      <Accordion.Header>
+        <Accordion.Trigger className="flex w-full items-center justify-between py-4 text-left text-sm font-medium">
+          Does Ledger support multiple currencies?
+          <ChevronDown className="size-4 shrink-0 text-muted-foreground" />
+        </Accordion.Trigger>
+      </Accordion.Header>
+      <Accordion.Panel className="pb-4 text-sm text-muted-foreground">
+        Ledger settles in 32 currencies and revalues open balances daily against the ECB reference rate.
+      </Accordion.Panel>
+    </Accordion.Item>
+  </Accordion.Root>
+);
+
+const AlertDialogStory: StoryRender = ({ sample }) => (
+  <AlertDialog.Root open onOpenChange={() => {}}>
+    <AlertDialog.Trigger className="rounded-md border border-border px-4 py-2 text-sm">Delete invoice</AlertDialog.Trigger>
+    <AlertDialog.Portal>
+      <AlertDialog.Backdrop className="fixed inset-0 bg-black/60" />
+      <AlertDialog.Popup className="fixed left-1/2 top-1/2 w-[360px] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-border bg-card p-6 text-card-foreground">
+        <AlertDialog.Title className="text-base font-semibold">
+          {sample.title ?? "Delete invoice INV-2041?"}
+        </AlertDialog.Title>
+        <AlertDialog.Description className="mt-2 text-sm text-muted-foreground">
+          This removes the invoice from Harbor CRM&apos;s billing history and notifies the client. This action cannot be
+          undone.
+        </AlertDialog.Description>
+        <div className="mt-6 flex justify-end gap-3">
+          <AlertDialog.Close className="rounded-md border border-border px-4 py-2 text-sm">Cancel</AlertDialog.Close>
+          <AlertDialog.Close className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground">
+            Delete invoice
+          </AlertDialog.Close>
+        </div>
+      </AlertDialog.Popup>
+    </AlertDialog.Portal>
+  </AlertDialog.Root>
+);
+
+const PROJECT_NAMES = [
+  "Northwind Redesign",
+  "Harbor CRM Migration",
+  "Q4 Billing Portal",
+  "Lantern Mobile App",
+  "Summit Data Warehouse",
+];
+
+const AutocompleteStory: StoryRender = ({ sample }) => (
+  <div className="grid w-full max-w-sm gap-2">
+    <label htmlFor="autocomplete-story-input" className="text-sm font-medium">
+      {sample.label ?? "Jump to project"}
+    </label>
+    <Autocomplete.Root items={PROJECT_NAMES} open onOpenChange={() => {}}>
+      <Autocomplete.Input
+        id="autocomplete-story-input"
+        placeholder="Search projects"
+        className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
+      />
+      <Autocomplete.Portal>
+        <Autocomplete.Positioner sideOffset={6} className="outline-none">
+          <Autocomplete.Popup className="min-w-[240px] rounded-md border border-border bg-popover p-1 text-popover-foreground">
+            <Autocomplete.Empty className="rounded-sm px-2 py-1.5 text-sm text-muted-foreground">
+              No projects found.
+            </Autocomplete.Empty>
+            <Autocomplete.List>
+              {(project: string) => (
+                <Autocomplete.Item key={project} value={project} className="rounded-sm px-2 py-1.5 text-sm">
+                  {project}
+                </Autocomplete.Item>
+              )}
+            </Autocomplete.List>
+          </Autocomplete.Popup>
+        </Autocomplete.Positioner>
+      </Autocomplete.Portal>
+    </Autocomplete.Root>
+  </div>
+);
+
+const AvatarStory: StoryRender = ({ sample }) => (
+  <div className="grid gap-4">
+    <div className="flex items-center gap-4">
+      <Avatar.Root className="flex size-12 items-center justify-center overflow-hidden rounded-full bg-muted">
+        <Avatar.Fallback delay={0} className="text-sm font-medium text-muted-foreground">
+          PR
+        </Avatar.Fallback>
+      </Avatar.Root>
+      <div>
+        <p className="text-sm font-medium">{sample.name ?? "Priya Raghavan"}</p>
+        <p className="text-sm text-muted-foreground">Product design — active 12 minutes ago</p>
+      </div>
+    </div>
+    <div className="flex items-center gap-4">
+      <Avatar.Root className="flex size-12 items-center justify-center overflow-hidden rounded-full bg-muted">
+        <Avatar.Fallback delay={0} className="text-sm font-medium text-muted-foreground">
+          MC
+        </Avatar.Fallback>
+      </Avatar.Root>
+      <div>
+        <p className="text-sm font-medium">Marcus Chen</p>
+        <p className="text-sm text-muted-foreground">Platform lead — active 2 hours ago</p>
+      </div>
+    </div>
+  </div>
+);
+
+const ButtonStory: StoryRender = ({ sample }) => (
+  <div className="flex flex-wrap items-center justify-center gap-3">
+    <Button className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground">
+      {sample.primaryLabel ?? "Start free trial"}
+    </Button>
+    <Button className="rounded-md border border-border px-4 py-2 text-sm">
+      {sample.secondaryLabel ?? "View the docs"}
+    </Button>
+  </div>
+);
+
+const CheckboxStory: StoryRender = ({ sample }) => (
+  <div className="grid gap-4">
+    {[sample.label ?? "Deployment failures", "Invoice reminders", "Weekly usage report"].map((label, index) => (
+      <label key={label} className="flex items-center gap-3 text-sm">
+        <Checkbox.Root
+          defaultChecked={index < 2}
+          className="flex size-5 items-center justify-center rounded-[4px] border border-border data-[checked]:bg-primary data-[checked]:text-primary-foreground"
+        >
+          <Checkbox.Indicator className="flex">
+            <Check className="size-3.5" />
+          </Checkbox.Indicator>
+        </Checkbox.Root>
+        {label}
+      </label>
+    ))}
+  </div>
+);
+
+const NOTIFICATION_CHANNELS = ["mentions", "direct messages", "project updates"];
+
+const CheckboxGroupStory: StoryRender = ({ sample }) => (
+  <CheckboxGroup
+    defaultValue={["mentions"]}
+    allValues={NOTIFICATION_CHANNELS}
+    aria-labelledby="checkbox-group-story-legend"
+    className="flex flex-col items-start gap-3"
+  >
+    <div id="checkbox-group-story-legend" className="text-sm font-medium">
+      {sample.groupLabel ?? "Email me about"}
+    </div>
+    <label className="flex items-center gap-3 text-sm">
+      <Checkbox.Root
+        parent
+        className="flex size-5 items-center justify-center rounded-[4px] border border-border data-[checked]:bg-primary data-[checked]:text-primary-foreground"
+      >
+        <Checkbox.Indicator
+          className="flex"
+          render={(props, state) => (
+            <span {...props}>{state.indeterminate ? <Minus className="size-3.5" /> : <Check className="size-3.5" />}</span>
+          )}
+        />
+      </Checkbox.Root>
+      All activity
+    </label>
+    {NOTIFICATION_CHANNELS.map((channel) => (
+      <label key={channel} className="flex items-center gap-3 text-sm">
+        <Checkbox.Root
+          value={channel}
+          className="flex size-5 items-center justify-center rounded-[4px] border border-border data-[checked]:bg-primary data-[checked]:text-primary-foreground"
+        >
+          <Checkbox.Indicator className="flex">
+            <Check className="size-3.5" />
+          </Checkbox.Indicator>
+        </Checkbox.Root>
+        {channel.charAt(0).toUpperCase() + channel.slice(1)}
+      </label>
+    ))}
+  </CheckboxGroup>
+);
+
+const CollapsibleStory: StoryRender = ({ sample }) => (
+  <Collapsible.Root defaultOpen className="w-full max-w-md">
+    <div className="flex items-center justify-between">
+      <h3 className="text-sm font-medium">{sample.triggerLabel ?? "Deployment details"}</h3>
+      <Collapsible.Trigger className="flex items-center gap-2 rounded-md border border-border px-3 py-1.5 text-sm">
+        Toggle
+        <ChevronDown className="size-4 text-muted-foreground" />
+      </Collapsible.Trigger>
+    </div>
+    <Collapsible.Panel className="mt-3 rounded-md border border-border p-4 text-sm text-muted-foreground">
+      <p>Commit a3f9c21 on main</p>
+      <p>Region iad1 — us-east</p>
+      <p>Finished in 2m 14s</p>
+    </Collapsible.Panel>
+  </Collapsible.Root>
+);
+
+const ASSIGNEE_NAMES = ["Priya Raghavan", "Marcus Chen", "Elena Petrova", "Tomás Alvarez", "Aisha Okonkwo"];
+
+const ComboboxStory: StoryRender = ({ sample }) => (
+  <div className="grid w-full max-w-sm gap-2">
+    <Combobox.Root items={ASSIGNEE_NAMES} open onOpenChange={() => {}}>
+      <Combobox.Label className="text-sm font-medium">{sample.label ?? "Assignee"}</Combobox.Label>
+      <Combobox.InputGroup className="mt-1 flex items-center gap-2 rounded-md border border-border bg-background px-3 py-2">
+        <Combobox.Input placeholder="Search teammates" className="flex-1 bg-transparent text-sm outline-none" />
+        <Combobox.Clear aria-label="Clear selection" className="text-muted-foreground">
+          <X className="size-4" />
+        </Combobox.Clear>
+        <Combobox.Trigger aria-label="Open list" className="text-muted-foreground">
+          <ChevronDown className="size-4" />
+        </Combobox.Trigger>
+      </Combobox.InputGroup>
+      <Combobox.Portal>
+        <Combobox.Positioner sideOffset={6} className="outline-none">
+          <Combobox.Popup className="min-w-[220px] rounded-md border border-border bg-popover p-1 text-popover-foreground">
+            <Combobox.Empty className="rounded-sm px-2 py-1.5 text-sm text-muted-foreground">
+              No teammates found.
+            </Combobox.Empty>
+            <Combobox.List>
+              {(assignee: string) => (
+                <Combobox.Item key={assignee} value={assignee} className="flex items-center justify-between rounded-sm px-2 py-1.5 text-sm">
+                  {assignee}
+                  <Combobox.ItemIndicator>
+                    <Check className="size-3.5" />
+                  </Combobox.ItemIndicator>
+                </Combobox.Item>
+              )}
+            </Combobox.List>
+          </Combobox.Popup>
+        </Combobox.Positioner>
+      </Combobox.Portal>
+    </Combobox.Root>
+  </div>
+);
+
+const ContextMenuStory: StoryRender = ({ sample }) => (
+  <ContextMenu.Root open onOpenChange={() => {}}>
+    <ContextMenu.Trigger className="flex h-48 w-full max-w-md flex-col items-center justify-center gap-1 rounded-xl border border-dashed border-border">
+      <span className="text-sm font-medium">{sample.projectName ?? "Northwind Redesign"}</span>
+      <span className="text-sm text-muted-foreground">Right-click a project row for actions</span>
+    </ContextMenu.Trigger>
+    <ContextMenu.Portal>
+      <ContextMenu.Positioner sideOffset={6} className="outline-none">
+        <ContextMenu.Popup className="min-w-[180px] rounded-md border border-border bg-popover p-1 text-popover-foreground">
+          <ContextMenu.Item className="rounded-sm px-2 py-1.5 text-sm">Open project</ContextMenu.Item>
+          <ContextMenu.Item className="rounded-sm px-2 py-1.5 text-sm">Open in new tab</ContextMenu.Item>
+          <ContextMenu.Separator className="my-1 h-px bg-border" />
+          <ContextMenu.Item className="rounded-sm px-2 py-1.5 text-sm">Duplicate</ContextMenu.Item>
+          <ContextMenu.Item className="rounded-sm px-2 py-1.5 text-sm">Move to archive</ContextMenu.Item>
+          <ContextMenu.Separator className="my-1 h-px bg-border" />
+          <ContextMenu.Item className="rounded-sm px-2 py-1.5 text-sm text-destructive">Delete project</ContextMenu.Item>
+        </ContextMenu.Popup>
+      </ContextMenu.Positioner>
+    </ContextMenu.Portal>
+  </ContextMenu.Root>
+);
+
+const DialogStory: StoryRender = ({ sample }) => (
+  <Dialog.Root open onOpenChange={() => {}}>
+    <Dialog.Trigger className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground">Invite member</Dialog.Trigger>
+    <Dialog.Portal>
+      <Dialog.Backdrop className="fixed inset-0 bg-black/60" />
+      <Dialog.Popup className="fixed left-1/2 top-1/2 w-[380px] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-border bg-card p-6 text-card-foreground">
+        <Dialog.Title className="text-base font-semibold">{sample.title ?? "Invite a team member"}</Dialog.Title>
+        <Dialog.Description className="mt-2 text-sm text-muted-foreground">
+          They receive an email with a link to join the Northwind workspace.
+        </Dialog.Description>
+        <Input
+          defaultValue="priya@northwind.io"
+          className="mt-4 w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
+        />
+        <div className="mt-6 flex justify-end gap-3">
+          <Dialog.Close className="rounded-md border border-border px-4 py-2 text-sm">Cancel</Dialog.Close>
+          <Dialog.Close className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground">Send invite</Dialog.Close>
+        </div>
+      </Dialog.Popup>
+    </Dialog.Portal>
+  </Dialog.Root>
+);
+
+const DrawerStory: StoryRender = ({ sample }) => (
+  <Drawer.Root swipeDirection="right" open onOpenChange={() => {}}>
+    <Drawer.Trigger className="rounded-md border border-border px-4 py-2 text-sm">Open settings</Drawer.Trigger>
+    <Drawer.Portal>
+      <Drawer.Backdrop className="fixed inset-0 bg-black/60" />
+      <Drawer.Viewport className="fixed inset-0 flex items-stretch justify-end">
+        <Drawer.Popup className="h-full w-[320px] max-w-[calc(100vw-2rem)] border-l border-border bg-card p-6 text-card-foreground">
+          <Drawer.Content className="mx-auto w-full">
+            <Drawer.Title className="text-base font-semibold">{sample.title ?? "Notification settings"}</Drawer.Title>
+            <Drawer.Description className="mt-2 text-sm text-muted-foreground">
+              Choose what this workspace sends you.
+            </Drawer.Description>
+            <div className="mt-6 grid gap-3 text-sm text-muted-foreground">
+              <p>Deployment alerts</p>
+              <p>Weekly summaries</p>
+              <p>Invoice reminders</p>
+            </div>
+            <Drawer.Close className="mt-6 rounded-md border border-border px-4 py-2 text-sm">Close</Drawer.Close>
+          </Drawer.Content>
+        </Drawer.Popup>
+      </Drawer.Viewport>
+    </Drawer.Portal>
+  </Drawer.Root>
+);
+
+const FieldStory: StoryRender = ({ sample }) => (
+  <Field.Root name="invoiceNumber" className="grid w-full max-w-sm gap-2">
+    <Field.Label className="text-sm font-medium">{sample.label ?? "Invoice number"}</Field.Label>
+    <Field.Control
+      pattern="INV-[0-9]{4}"
+      defaultValue="2041-AR"
+      className="rounded-md border border-border bg-background px-3 py-2 text-sm"
+    />
+    <Field.Error match="patternMismatch" className="text-sm text-destructive">
+      Use the format INV-2041.
+    </Field.Error>
+    <Field.Description className="text-sm text-muted-foreground">
+      Printed on every invoice PDF and receipt.
+    </Field.Description>
+  </Field.Root>
+);
+
+const FieldsetStory: StoryRender = ({ sample }) => (
+  <Fieldset.Root className="grid w-full max-w-sm gap-4">
+    <Fieldset.Legend className="text-sm font-medium">{sample.legend ?? "Shipping address"}</Fieldset.Legend>
+    <Field.Root name="street" className="grid gap-2">
+      <Field.Label className="text-sm font-medium">Street address</Field.Label>
+      <Field.Control defaultValue="1 Harbour Road" className="rounded-md border border-border bg-background px-3 py-2 text-sm" />
+    </Field.Root>
+    <Field.Root name="city" className="grid gap-2">
+      <Field.Label className="text-sm font-medium">City</Field.Label>
+      <Field.Control defaultValue="Bristol" className="rounded-md border border-border bg-background px-3 py-2 text-sm" />
+    </Field.Root>
+    <Field.Root name="postcode" className="grid gap-2">
+      <Field.Label className="text-sm font-medium">Postcode</Field.Label>
+      <Field.Control defaultValue="BS1 4ST" className="rounded-md border border-border bg-background px-3 py-2 text-sm" />
+    </Field.Root>
+  </Fieldset.Root>
+);
+
+const FormStory: StoryRender = ({ sample }) => (
+  <Form className="grid w-full max-w-sm gap-4">
+    <Field.Root name="email" className="grid gap-2">
+      <Field.Label className="text-sm font-medium">Work email</Field.Label>
+      <Field.Control
+        type="email"
+        defaultValue="priya at northwind.io"
+        className="rounded-md border border-border bg-background px-3 py-2 text-sm"
+      />
+      <Field.Error match="typeMismatch" className="text-sm text-destructive">
+        Enter a valid email address.
+      </Field.Error>
+    </Field.Root>
+    <Button type="submit" className="w-fit rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground">
+      {sample.submitLabel ?? "Create workspace"}
+    </Button>
+  </Form>
+);
+
+const InputStory: StoryRender = ({ sample }) => (
+  <div className="grid w-full max-w-sm gap-2">
+    <label htmlFor="input-story-email" className="text-sm font-medium">
+      {sample.label ?? "Work email"}
+    </label>
+    <Input
+      id="input-story-email"
+      type="email"
+      placeholder="you@example.com"
+      className="rounded-md border border-border bg-background px-3 py-2 text-sm"
+    />
+  </div>
+);
+
+const MenuStory: StoryRender = ({ sample }) => (
+  <Menu.Root open onOpenChange={() => {}}>
+    <Menu.Trigger className="rounded-md border border-border px-4 py-2 text-sm">
+      {sample.triggerLabel ?? "Project actions"}
+    </Menu.Trigger>
+    <Menu.Portal>
+      <Menu.Positioner sideOffset={6} className="outline-none">
+        <Menu.Popup className="min-w-[180px] rounded-md border border-border bg-popover p-1 text-popover-foreground">
+          <Menu.Group>
+            <Menu.GroupLabel className="px-2 py-1.5 text-sm text-muted-foreground">Northwind Redesign</Menu.GroupLabel>
+            <Menu.Item className="rounded-sm px-2 py-1.5 text-sm">Open in new tab</Menu.Item>
+            <Menu.Item className="rounded-sm px-2 py-1.5 text-sm">Duplicate</Menu.Item>
+          </Menu.Group>
+          <Menu.Separator className="my-1 h-px bg-border" />
+          <Menu.Item className="rounded-sm px-2 py-1.5 text-sm">Move to archive</Menu.Item>
+          <Menu.Item className="rounded-sm px-2 py-1.5 text-sm text-destructive">Delete project</Menu.Item>
+        </Menu.Popup>
+      </Menu.Positioner>
+    </Menu.Portal>
+  </Menu.Root>
+);
+
+const MenubarStory: StoryRender = ({ sample }) => (
+  <Menubar className="flex rounded-md border border-border p-1">
+    <Menu.Root open onOpenChange={() => {}}>
+      <Menu.Trigger className="rounded-sm px-3 py-1.5 text-sm">{sample.menuLabel ?? "File"}</Menu.Trigger>
+      <Menu.Portal>
+        <Menu.Positioner sideOffset={6} className="outline-none">
+          <Menu.Popup className="min-w-[180px] rounded-md border border-border bg-popover p-1 text-popover-foreground">
+            <Menu.Item className="rounded-sm px-2 py-1.5 text-sm">New invoice</Menu.Item>
+            <Menu.Item className="rounded-sm px-2 py-1.5 text-sm">Open workspace…</Menu.Item>
+            <Menu.Separator className="my-1 h-px bg-border" />
+            <Menu.Item className="rounded-sm px-2 py-1.5 text-sm">Import from Stripe</Menu.Item>
+          </Menu.Popup>
+        </Menu.Positioner>
+      </Menu.Portal>
+    </Menu.Root>
+    <Menu.Root>
+      <Menu.Trigger className="rounded-sm px-3 py-1.5 text-sm">Edit</Menu.Trigger>
+      <Menu.Portal>
+        <Menu.Positioner sideOffset={6} className="outline-none">
+          <Menu.Popup className="min-w-[180px] rounded-md border border-border bg-popover p-1 text-popover-foreground">
+            <Menu.Item className="rounded-sm px-2 py-1.5 text-sm">Undo</Menu.Item>
+            <Menu.Item className="rounded-sm px-2 py-1.5 text-sm">Redo</Menu.Item>
+            <Menu.Separator className="my-1 h-px bg-border" />
+            <Menu.Item className="rounded-sm px-2 py-1.5 text-sm">Duplicate invoice</Menu.Item>
+          </Menu.Popup>
+        </Menu.Positioner>
+      </Menu.Portal>
+    </Menu.Root>
+    <Menu.Root>
+      <Menu.Trigger className="rounded-sm px-3 py-1.5 text-sm">View</Menu.Trigger>
+      <Menu.Portal>
+        <Menu.Positioner sideOffset={6} className="outline-none">
+          <Menu.Popup className="min-w-[180px] rounded-md border border-border bg-popover p-1 text-popover-foreground">
+            <Menu.Item className="rounded-sm px-2 py-1.5 text-sm">Invoices</Menu.Item>
+            <Menu.Item className="rounded-sm px-2 py-1.5 text-sm">Reports</Menu.Item>
+          </Menu.Popup>
+        </Menu.Positioner>
+      </Menu.Portal>
+    </Menu.Root>
+  </Menubar>
+);
+
+const MeterStory: StoryRender = ({ sample }) => (
+  <div className="w-full max-w-md">
+    <Meter.Root
+      value={68.4}
+      max={100}
+      format={{ style: "unit", unit: "gigabyte", unitDisplay: "narrow", maximumFractionDigits: 1 }}
+      className="grid grid-cols-2 gap-y-2"
+    >
+      <Meter.Label className="text-sm font-medium">{sample.label ?? "Storage used"}</Meter.Label>
+      <Meter.Value className="text-right text-sm text-muted-foreground" />
+      <Meter.Track className="col-span-2 h-2 overflow-hidden rounded-full bg-muted">
+        <Meter.Indicator className="h-full rounded-full bg-primary" />
+      </Meter.Track>
+    </Meter.Root>
+    <p className="mt-2 text-sm text-muted-foreground">31.6 GB free on the Northwind workspace</p>
+  </div>
+);
+
+export const storyRenders: Record<string, StoryRender> = {
+  "base-ui/Accordion": AccordionStory,
+  "base-ui/Alert Dialog": AlertDialogStory,
+  "base-ui/Autocomplete": AutocompleteStory,
+  "base-ui/Avatar": AvatarStory,
+  "base-ui/Button": ButtonStory,
+  "base-ui/Checkbox": CheckboxStory,
+  "base-ui/Checkbox Group": CheckboxGroupStory,
+  "base-ui/Collapsible": CollapsibleStory,
+  "base-ui/Combobox": ComboboxStory,
+  "base-ui/Context Menu": ContextMenuStory,
+  "base-ui/Dialog": DialogStory,
+  "base-ui/Drawer": DrawerStory,
+  "base-ui/Field": FieldStory,
+  "base-ui/Fieldset": FieldsetStory,
+  "base-ui/Form": FormStory,
+  "base-ui/Input": InputStory,
+  "base-ui/Menu": MenuStory,
+  "base-ui/Menubar": MenubarStory,
+  "base-ui/Meter": MeterStory,
+};
```

## components/preview-stories/base-ui-2.tsx

Status: added; +488; −0

```diff
@@ -1,0 +1,488 @@
+import { Check, ChevronDown, Minus, Plus } from "lucide-react";
+import {
+  NavigationMenu,
+  NumberField,
+  OTPField,
+  Popover,
+  PreviewCard,
+  Progress,
+  Radio,
+  RadioGroup,
+  ScrollArea,
+  Select,
+  Separator,
+  Slider,
+  Switch,
+  Tabs,
+  Toast,
+  Toggle,
+  ToggleGroup,
+  Toolbar,
+  Tooltip,
+} from "@base-ui/react";
+import type { StoryRender } from "./type";
+
+/**
+ * Preview stories for Base UI (batch 2): each documented unstyled primitive
+ * assembled the way the library's docs assemble it — accessible behaviour from
+ * the real package, Col's theme tokens for the look. Overlay primitives are
+ * held open so a static screenshot shows the popup, not just the trigger, and
+ * sample strings replace the display text where a caller labels the render.
+ */
+
+const selectEnvironments = [
+  { value: "production", label: "Production" },
+  { value: "staging", label: "Staging" },
+  { value: "preview", label: "Preview · pr-812" },
+  { value: "development", label: "Development" },
+];
+
+const NavigationMenuStory: StoryRender = ({ sample }) => (
+  <NavigationMenu.Root defaultValue="platform" className="text-sm">
+    <NavigationMenu.List className="flex gap-1 rounded-md border border-border p-1">
+      <NavigationMenu.Item value="platform">
+        <NavigationMenu.Trigger className="flex items-center gap-1.5 rounded-sm px-3 py-1.5 text-sm">
+          {sample.menuItem1 ?? "Platform"}
+          <NavigationMenu.Icon>
+            <ChevronDown className="size-4 text-muted-foreground" />
+          </NavigationMenu.Icon>
+        </NavigationMenu.Trigger>
+        <NavigationMenu.Content className="h-full w-max max-w-[400px] p-2">
+          <ul className="m-0 grid list-none grid-cols-2 gap-1 p-0">
+            {[
+              ["Observability", "Metrics, logs, and traces in one place."],
+              ["Incident response", "On-call rotations and alert routing."],
+              ["Status pages", "Public updates for your customers."],
+              ["Audit log", "Every change to production, recorded."],
+            ].map(([title, description]) => (
+              <li key={title}>
+                <NavigationMenu.Link
+                  href={`#${title.toLowerCase().replace(/\s+/g, "-")}`}
+                  className="block rounded-sm p-2 text-inherit no-underline"
+                >
+                  <p className="text-sm font-medium">{title}</p>
+                  <p className="text-sm text-muted-foreground">{description}</p>
+                </NavigationMenu.Link>
+              </li>
+            ))}
+          </ul>
+        </NavigationMenu.Content>
+      </NavigationMenu.Item>
+      <NavigationMenu.Item value="docs">
+        <NavigationMenu.Trigger className="rounded-sm px-3 py-1.5 text-sm">
+          {sample.menuItem2 ?? "Docs"}
+        </NavigationMenu.Trigger>
+      </NavigationMenu.Item>
+      <NavigationMenu.Item value="pricing">
+        <NavigationMenu.Trigger className="rounded-sm px-3 py-1.5 text-sm">
+          {sample.menuItem3 ?? "Pricing"}
+        </NavigationMenu.Trigger>
+      </NavigationMenu.Item>
+    </NavigationMenu.List>
+    <NavigationMenu.Portal>
+      <NavigationMenu.Positioner
+        sideOffset={8}
+        className="h-[var(--positioner-height)] w-[var(--positioner-width)]"
+      >
+        <NavigationMenu.Popup className="h-[var(--popup-height)] w-[var(--popup-width)] rounded-md border border-border bg-popover text-popover-foreground">
+          <NavigationMenu.Viewport className="relative h-full w-full overflow-hidden" />
+        </NavigationMenu.Popup>
+      </NavigationMenu.Positioner>
+    </NavigationMenu.Portal>
+  </NavigationMenu.Root>
+);
+
+const NumberFieldStory: StoryRender = ({ sample }) => (
+  <div className="grid w-full max-w-sm gap-2">
+    <label htmlFor="number-field-story-seats" className="text-sm font-medium">
+      {sample.label ?? "Team seats"}
+    </label>
+    <NumberField.Root
+      id="number-field-story-seats"
+      defaultValue={24}
+      min={1}
+      max={500}
+      className="flex items-center gap-3"
+    >
+      <NumberField.Group className="flex h-10 items-stretch">
+        <NumberField.Decrement className="flex w-10 items-center justify-center rounded-l-md border border-border">
+          <Minus className="size-4" />
+        </NumberField.Decrement>
+        <NumberField.Input className="w-20 border-y border-border bg-background px-3 text-center text-sm tabular-nums" />
+        <NumberField.Increment className="flex w-10 items-center justify-center rounded-r-md border border-border">
+          <Plus className="size-4" />
+        </NumberField.Increment>
+      </NumberField.Group>
+      <p className="text-sm text-muted-foreground">{sample.hint ?? "Billed at $18 per seat each month."}</p>
+    </NumberField.Root>
+  </div>
+);
+
+const OTPFieldStory: StoryRender = ({ sample }) => (
+  <div className="grid w-full max-w-sm gap-2">
+    <label htmlFor="otp-field-story-code" className="text-sm font-medium">
+      {sample.label ?? "Verification code"}
+    </label>
+    <OTPField.Root id="otp-field-story-code" length={6} defaultValue="4829" className="flex gap-2">
+      {Array.from({ length: 6 }, (_, index) => (
+        <OTPField.Input
+          key={index}
+          aria-label={`Character ${index + 1} of 6`}
+          className="size-11 rounded-md border border-border bg-background text-center text-lg tabular-nums"
+        />
+      ))}
+    </OTPField.Root>
+    <p className="text-sm text-muted-foreground">
+      {sample.hint ?? "Enter the 6-digit code we sent to +1 (415) 555-0132."}
+    </p>
+  </div>
+);
+
+const PopoverStory: StoryRender = ({ sample }) => (
+  <Popover.Root defaultOpen>
+    <Popover.Trigger className="rounded-md border border-border px-4 py-2 text-sm">
+      {sample.triggerLabel ?? "Invite teammate"}
+    </Popover.Trigger>
+    <Popover.Portal>
+      <Popover.Positioner sideOffset={8}>
+        <Popover.Popup className="w-72 rounded-xl border border-border bg-popover p-4 text-popover-foreground">
+          <Popover.Title className="text-sm font-semibold">
+            {sample.title ?? "Invite to Northwind Q3 forecast"}
+          </Popover.Title>
+          <Popover.Description className="mt-2 text-sm text-muted-foreground">
+            {sample.description ?? "They get view access to the shared workspace."}
+          </Popover.Description>
+          <input
+            aria-label="Teammate email"
+            defaultValue="priya@northwind.io"
+            className="mt-3 w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
+          />
+          <div className="mt-4 flex justify-end gap-2">
+            <Popover.Close className="rounded-md border border-border px-3 py-1.5 text-sm">Cancel</Popover.Close>
+            <Popover.Close className="rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground">
+              Send invite
+            </Popover.Close>
+          </div>
+        </Popover.Popup>
+      </Popover.Positioner>
+    </Popover.Portal>
+  </Popover.Root>
+);
+
+const PreviewCardStory: StoryRender = ({ sample }) => (
+  <PreviewCard.Root defaultOpen>
+    <p className="max-w-md text-sm">
+      The billing migration is led by{" "}
+      <PreviewCard.Trigger
+        href="#priya-raghavan"
+        className="font-medium underline underline-offset-4"
+      >
+        {sample.name ?? "Priya Raghavan"}
+      </PreviewCard.Trigger>
+      , staff engineer on the platform team.
+    </p>
+    <PreviewCard.Portal>
+      <PreviewCard.Positioner sideOffset={8}>
+        <PreviewCard.Popup className="w-72 rounded-xl border border-border bg-popover p-4 text-popover-foreground">
+          <div className="flex items-center gap-3">
+            <div className="flex size-10 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground">
+              PR
+            </div>
+            <div>
+              <p className="text-sm font-semibold">{sample.name ?? "Priya Raghavan"}</p>
+              <p className="text-sm text-muted-foreground">{sample.role ?? "Staff engineer, Platform"}</p>
+            </div>
+          </div>
+          <p className="mt-3 text-sm text-muted-foreground">
+            Owns the invoice pipeline and the migration to Northwind Ledger.
+          </p>
+        </PreviewCard.Popup>
+      </PreviewCard.Positioner>
+    </PreviewCard.Portal>
+  </PreviewCard.Root>
+);
+
+const ProgressStory: StoryRender = ({ sample }) => (
+  <Progress.Root
+    value={184000}
+    max={250000}
+    format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }}
+    className="grid w-full max-w-md grid-cols-2 gap-y-2"
+  >
+    <Progress.Label className="text-sm">{sample.label ?? "Q3 revenue target"}</Progress.Label>
+    <Progress.Value className="text-right text-sm text-muted-foreground" />
+    <Progress.Track className="col-span-2 h-2 overflow-hidden rounded-full bg-muted">
+      <Progress.Indicator className="h-full rounded-full bg-primary" />
+    </Progress.Track>
+  </Progress.Root>
+);
+
+const RadioGroupStory: StoryRender = ({ sample }) => (
+  <RadioGroup defaultValue="canary" className="grid gap-3">
+    <p className="text-sm font-medium">{sample.legend ?? "Deployment strategy"}</p>
+    {[
+      ["rolling", "Rolling", "Replaces instances gradually with zero downtime."],
+      ["blue-green", "Blue-green", "Keeps a standby environment for instant cutovers."],
+      ["canary", "Canary", "Shifts 5% of traffic to the new build before the full rollout."],
+    ].map(([value, title, description]) => (
+      <label key={value} className="flex items-start gap-3">
+        <Radio.Root value={value} className="mt-0.5 flex size-5 items-center justify-center rounded-full border border-border">
+          <Radio.Indicator className="size-2.5 rounded-full bg-primary" />
+        </Radio.Root>
+        <span className="grid gap-0.5">
+          <span className="text-sm font-medium">{title}</span>
+          <span className="text-sm text-muted-foreground">{description}</span>
+        </span>
+      </label>
+    ))}
+  </RadioGroup>
+);
+
+const ScrollAreaStory: StoryRender = ({ sample }) => (
+  <div className="w-full max-w-sm">
+    <p className="mb-2 text-sm font-medium">{sample.label ?? "Recent activity"}</p>
+    <ScrollArea.Root className="h-56 w-full rounded-xl border border-border">
+      <ScrollArea.Viewport className="h-full">
+        <ScrollArea.Content className="grid gap-3 p-4 text-sm">
+          {[
+            ["Ada Nakamura", "approved invoice #1042", "2 min ago"],
+            ["Priya Raghavan", "deployed northwind-web to production", "26 min ago"],
+            ["Marcus Bell", "changed the incident rotation", "1 hour ago"],
+            ["Sofia Almeida", "merged billing migration #812", "3 hours ago"],
+            ["Ada Nakamura", "refunded invoice #0978", "5 hours ago"],
+            ["Ken Watanabe", "added three seats to Northwind", "Yesterday"],
+            ["Priya Raghavan", "rotated the staging credentials", "Yesterday"],
+            ["Sofia Almeida", "published the March usage report", "2 days ago"],
+          ].map(([person, action, time]) => (
+            <p key={`${person}-${action}`}>
+              <span className="font-medium">{person}</span>{" "}
+              <span className="text-muted-foreground">
+                {action} · {time}
+              </span>
+            </p>
+          ))}
+        </ScrollArea.Content>
+      </ScrollArea.Viewport>
+      <ScrollArea.Scrollbar className="m-1 flex w-2 justify-center rounded-full bg-muted">
+        <ScrollArea.Thumb className="w-full rounded-full bg-border" />
+      </ScrollArea.Scrollbar>
+    </ScrollArea.Root>
+  </div>
+);
+
+const SelectStory: StoryRender = ({ sample }) => (
+  <div className="grid w-full max-w-sm gap-2">
+    <Select.Root items={selectEnvironments} defaultValue="staging" defaultOpen>
+      <Select.Label className="text-sm font-medium">{sample.label ?? "Deploy environment"}</Select.Label>
+      <Select.Trigger className="flex items-center justify-between gap-3 rounded-md border border-border bg-background px-3 py-2 text-sm">
+        <Select.Value />
+        <Select.Icon>
+          <ChevronDown className="size-4 text-muted-foreground" />
+        </Select.Icon>
+      </Select.Trigger>
+      <Select.Portal>
+        <Select.Positioner sideOffset={4}>
+          <Select.Popup className="min-w-[var(--anchor-width)] rounded-md border border-border bg-popover p-1 text-popover-foreground">
+            <Select.List>
+              {selectEnvironments.map((environment) => (
+                <Select.Item
+                  key={environment.value}
+                  value={environment.value}
+                  className="grid grid-cols-[1rem_1fr] items-center gap-2 rounded-sm px-2 py-1.5 text-sm"
+                >
+                  <Select.ItemIndicator className="col-start-1">
+                    <Check className="size-3.5" />
+                  </Select.ItemIndicator>
+                  <Select.ItemText className="col-start-2">{environment.label}</Select.ItemText>
+                </Select.Item>
+              ))}
+            </Select.List>
+          </Select.Popup>
+        </Select.Positioner>
+      </Select.Portal>
+    </Select.Root>
+  </div>
+);
+
+const SeparatorStory: StoryRender = ({ sample }) => (
+  <div className="w-full max-w-md">
+    <p className="text-sm font-medium">{sample.title ?? "Northwind Analytics"}</p>
+    <Separator className="my-4 h-px bg-border" />
+    <p className="text-sm text-muted-foreground">
+      {sample.description ?? "Real-time revenue tracking for subscription teams."}
+    </p>
+  </div>
+);
+
+const SliderStory: StoryRender = ({ sample }) => (
+  <Slider.Root
+    defaultValue={4200}
+    min={0}
+    max={10000}
+    step={100}
+    format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }}
+    className="w-full max-w-md"
+  >
+    <div className="mb-1 flex items-baseline justify-between text-sm">
+      <Slider.Label>{sample.label ?? "Monthly ad spend"}</Slider.Label>
+      <Slider.Value className="text-muted-foreground" />
+    </div>
+    <Slider.Control className="flex touch-none items-center py-3 select-none">
+      <Slider.Track className="h-2 w-full rounded-full bg-muted">
+        <Slider.Indicator className="h-full rounded-full bg-primary" />
+        <Slider.Thumb aria-label="Ad spend" className="size-5 rounded-full border-2 border-primary bg-background" />
+      </Slider.Track>
+    </Slider.Control>
+  </Slider.Root>
+);
+
+const SwitchStory: StoryRender = ({ sample }) => (
+  <label className="flex items-center gap-3">
+    <Switch.Root
+      defaultChecked
+      className="flex h-6 w-11 shrink-0 items-center rounded-full bg-muted p-1 data-[checked]:bg-primary"
+    >
+      <Switch.Thumb className="block size-4 rounded-full bg-foreground transition-[translate,background-color] data-[checked]:translate-x-5 data-[checked]:bg-primary-foreground" />
+    </Switch.Root>
+    <span className="grid gap-0.5">
+      <span className="text-sm font-medium">{sample.label ?? "Auto-deploy on merge"}</span>
+      <span className="text-sm text-muted-foreground">
+        {sample.description ?? "Deploys to staging after every merge to main."}
+      </span>
+    </span>
+  </label>
+);
+
+const tabClassName =
+  "border-b-2 border-transparent px-3 py-2 text-sm text-muted-foreground data-[active]:border-primary data-[active]:text-foreground";
+
+const TabsStory: StoryRender = ({ sample }) => (
+  <Tabs.Root defaultValue="invoices" className="w-full max-w-md">
+    <Tabs.List className="flex gap-2 border-b border-border">
+      <Tabs.Tab value="invoices" className={tabClassName}>
+        {sample.tab1 ?? "Invoices"}
+      </Tabs.Tab>
+      <Tabs.Tab value="subscriptions" className={tabClassName}>
+        {sample.tab2 ?? "Subscriptions"}
+      </Tabs.Tab>
+      <Tabs.Tab value="payouts" className={tabClassName}>
+        {sample.tab3 ?? "Payouts"}
+      </Tabs.Tab>
+    </Tabs.List>
+    <Tabs.Panel value="invoices" className="pt-4 text-sm text-muted-foreground">
+      12 open invoices — $48,210 outstanding across 9 customers.
+    </Tabs.Panel>
+    <Tabs.Panel value="subscriptions" className="pt-4 text-sm text-muted-foreground">
+      1,284 active subscriptions — $92,400 in monthly recurring revenue.
+    </Tabs.Panel>
+    <Tabs.Panel value="payouts" className="pt-4 text-sm text-muted-foreground">
+      The next payout of $18,320 clears Friday to First Meridian Bank.
+    </Tabs.Panel>
+  </Tabs.Root>
+);
+
+const ToastStory: StoryRender = ({ sample }) => (
+  <Toast.Provider timeout={0}>
+    <Toast.Portal>
+      <Toast.Viewport className="fixed bottom-4 right-4 w-80">
+        <Toast.Root
+          toast={{
+            id: "invoice-paid",
+            title: sample.title ?? "Invoice #1042 paid",
+            description: sample.description ?? "Acme Corp paid $4,820 — two minutes ago.",
+            timeout: 0,
+          }}
+          className="rounded-xl border border-border bg-popover p-4 text-popover-foreground"
+        >
+          <Toast.Content>
+            <Toast.Title className="text-sm font-semibold" />
+            <Toast.Description className="mt-1 text-sm text-muted-foreground" />
+            <div className="mt-3 flex gap-2">
+              <Toast.Action className="rounded-md border border-border px-3 py-1.5 text-sm">
+                View invoice
+              </Toast.Action>
+              <Toast.Close className="rounded-md px-3 py-1.5 text-sm text-muted-foreground">Dismiss</Toast.Close>
+            </div>
+          </Toast.Content>
+        </Toast.Root>
+      </Toast.Viewport>
+    </Toast.Portal>
+  </Toast.Provider>
+);
+
+const ToggleStory: StoryRender = ({ sample }) => (
+  <Toggle defaultPressed className="rounded-md border border-border px-4 py-2 text-sm data-[pressed]:bg-secondary">
+    {sample.label ?? "Bold"}
+  </Toggle>
+);
+
+const ToggleGroupStory: StoryRender = ({ sample }) => (
+  <ToggleGroup defaultValue={["week"]} aria-label="Time range" className="flex rounded-md border border-border p-1">
+    {[
+      ["day", sample.option1 ?? "Day"],
+      ["week", sample.option2 ?? "Week"],
+      ["month", sample.option3 ?? "Month"],
+    ].map(([value, label]) => (
+      <Toggle key={value} value={value} className="rounded-sm px-3 py-1.5 text-sm data-[pressed]:bg-secondary">
+        {label}
+      </Toggle>
+    ))}
+  </ToggleGroup>
+);
+
+const ToolbarStory: StoryRender = ({ sample }) => (
+  <Toolbar.Root className="flex w-full max-w-xl items-center gap-1 rounded-md border border-border p-1">
+    <Toolbar.Button className="rounded-sm px-3 py-1.5 text-sm">{sample.button1 ?? "Bold"}</Toolbar.Button>
+    <Toolbar.Button className="rounded-sm px-3 py-1.5 text-sm">{sample.button2 ?? "Italic"}</Toolbar.Button>
+    <Toolbar.Separator className="mx-1 h-5 w-px bg-border" />
+    <Toolbar.Group aria-label="History" className="flex">
+      <Toolbar.Button className="rounded-sm px-3 py-1.5 text-sm">Undo</Toolbar.Button>
+      <Toolbar.Button className="rounded-sm px-3 py-1.5 text-sm">Redo</Toolbar.Button>
+    </Toolbar.Group>
+    <Toolbar.Separator className="mx-1 h-5 w-px bg-border" />
+    <Toolbar.Input
+      aria-label="Link URL"
+      defaultValue="https://northwind.io/q3-forecast"
+      className="w-56 rounded-md border border-border bg-background px-3 py-1.5 text-sm"
+    />
+    <Toolbar.Button className="rounded-sm px-3 py-1.5 text-sm">Apply</Toolbar.Button>
+  </Toolbar.Root>
+);
+
+const TooltipStory: StoryRender = ({ sample }) => (
+  <Tooltip.Provider>
+    <Tooltip.Root open onOpenChange={() => {}}>
+      <Tooltip.Trigger className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground">
+        {sample.triggerLabel ?? "Start free trial"}
+      </Tooltip.Trigger>
+      <Tooltip.Portal>
+        <Tooltip.Positioner sideOffset={8}>
+          <Tooltip.Popup className="rounded-md border border-border bg-popover px-3 py-1.5 text-sm text-popover-foreground">
+            {sample.tooltip ?? "14 days free — no credit card required."}
+          </Tooltip.Popup>
+        </Tooltip.Positioner>
+      </Tooltip.Portal>
+    </Tooltip.Root>
+  </Tooltip.Provider>
+);
+
+export const storyRenders: Record<string, StoryRender> = {
+  "base-ui/Navigation Menu": NavigationMenuStory,
+  "base-ui/Number Field": NumberFieldStory,
+  "base-ui/OTP Field": OTPFieldStory,
+  "base-ui/Popover": PopoverStory,
+  "base-ui/Preview Card": PreviewCardStory,
+  "base-ui/Progress": ProgressStory,
+  "base-ui/Radio Group": RadioGroupStory,
+  "base-ui/Scroll Area": ScrollAreaStory,
+  "base-ui/Select": SelectStory,
+  "base-ui/Separator": SeparatorStory,
+  "base-ui/Slider": SliderStory,
+  "base-ui/Switch": SwitchStory,
+  "base-ui/Tabs": TabsStory,
+  "base-ui/Toast": ToastStory,
+  "base-ui/Toggle": ToggleStory,
+  "base-ui/Toggle Group": ToggleGroupStory,
+  "base-ui/Toolbar": ToolbarStory,
+  "base-ui/Tooltip": TooltipStory,
+};
```

## components/preview-stories/elevenlabs-ui.tsx

Status: added; +1029; −0

```diff
@@ -1,0 +1,1029 @@
+import * as React from "react";
+import { DropdownMenu, Slider } from "radix-ui";
+import { ArrowDown, Check, MicIcon, PauseIcon, PlayIcon, Settings, X } from "lucide-react";
+import type { StoryRender } from "./type";
+
+/**
+ * Preview stories for ElevenLabs UI. The library is a shadcn-style registry
+ * (no npm package), so each documented component is implemented here from the
+ * official docs at ui.elevenlabs.io, on top of Radix UI + Tailwind with Col's
+ * theme tokens. Audio-reactive pieces (the WebGL Orb, live microphone capture)
+ * render a deterministic still of their documented visual state — the preview
+ * canvas captures static frames and never runs continuous animation.
+ */
+
+// ---------------------------------------------------------------------------
+// Shared helpers
+// ---------------------------------------------------------------------------
+
+function classNames(...values: Array<string | false | undefined>): string {
+  return values.filter(Boolean).join(" ");
+}
+
+/** Deterministic pseudo-random source so seeded visuals stay identical across renders. */
+function seededRandom(seed: number): () => number {
+  let state = seed >>> 0;
+  return () => {
+    state = (state + 0x6d2b79f5) >>> 0;
+    let t = state;
+    t = Math.imul(t ^ (t >>> 15), t | 1);
+    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
+    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
+  };
+}
+
+function formatTime(seconds: number): string {
+  if (!Number.isFinite(seconds) || seconds <= 0) return "0:00";
+  const minutes = Math.floor(seconds / 60);
+  const rest = Math.floor(seconds % 60);
+  return `${minutes}:${rest.toString().padStart(2, "0")}`;
+}
+
+function drawRoundedBar(
+  context: CanvasRenderingContext2D,
+  x: number,
+  centerY: number,
+  width: number,
+  height: number,
+  radius: number,
+): void {
+  const half = Math.max(height / 2, 0.5);
+  const r = Math.min(radius, half);
+  context.beginPath();
+  context.moveTo(x, centerY - half);
+  context.lineTo(x + width, centerY - half);
+  context.quadraticCurveTo(x + width + r, centerY - half, x + width + r, centerY - half + r);
+  context.lineTo(x + width + r, centerY + half - r);
+  context.quadraticCurveTo(x + width + r, centerY + half, x + width, centerY + half);
+  context.lineTo(x, centerY + half);
+  context.quadraticCurveTo(x - r, centerY + half, x - r, centerY + half - r);
+  context.lineTo(x - r, centerY - half + r);
+  context.quadraticCurveTo(x - r, centerY - half, x, centerY - half);
+  context.closePath();
+  context.fill();
+}
+
+/** Paints one waveform frame: symmetric bars with optional edge fade. */
+function drawWaveformFrame(
+  canvas: HTMLCanvasElement,
+  values: readonly number[],
+  options: {
+    barWidth: number;
+    barHeight: number;
+    barGap: number;
+    barRadius: number;
+    barColor: string;
+    fadeEdges: boolean;
+    fadeWidth: number;
+  },
+): void {
+  const context = canvas.getContext("2d");
+  if (!context) return;
+  const ratio = window.devicePixelRatio || 1;
+  const width = canvas.clientWidth;
+  const height = canvas.clientHeight;
+  canvas.width = Math.max(Math.floor(width * ratio), 1);
+  canvas.height = Math.max(Math.floor(height * ratio), 1);
+  context.setTransform(ratio, 0, 0, ratio, 0, 0);
+  context.clearRect(0, 0, width, height);
+  context.fillStyle = options.barColor;
+
+  const centerY = height / 2;
+  const slot = options.barWidth + options.barGap;
+  const bars = Math.min(values.length, Math.max(Math.floor(width / slot), 1));
+  for (let index = 0; index < bars; index += 1) {
+    const value = Math.max(Math.min(values[index] ?? 0, 1), 0);
+    const barHeight = Math.max(value * (height - options.barHeight) + options.barHeight, options.barHeight);
+    if (options.fadeEdges) {
+      const x = index * slot;
+      const edge = Math.min(x, width - (x + options.barWidth));
+      const fade = options.fadeWidth > 0 ? Math.max(Math.min(edge / options.fadeWidth, 1), 0) : 1;
+      context.globalAlpha = 0.15 + 0.85 * fade;
+    }
+    drawRoundedBar(context, index * slot, centerY, options.barWidth, barHeight, options.barRadius);
+    context.globalAlpha = 1;
+  }
+}
+
+/** Resolves the bar color the docs describe: `barColor` or the theme's foreground. */
+function resolveBarColor(barColor: string | undefined): string {
+  if (barColor) return barColor;
+  if (typeof window === "undefined") return "#ffffff";
+  return getComputedStyle(document.documentElement).getPropertyValue("--foreground").trim() || "#ffffff";
+}
+
+// ---------------------------------------------------------------------------
+// Small in-module button base (the registry's Button the docs build on)
+// ---------------------------------------------------------------------------
+
+type ButtonVariant = "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
+type ButtonSize = "default" | "sm" | "lg" | "icon";
+
+type ButtonProps = React.ComponentPropsWithoutRef<"button"> & {
+  variant?: ButtonVariant;
+  size?: ButtonSize;
+};
+
+const buttonVariants: Record<ButtonVariant, string> = {
+  default: "bg-primary text-primary-foreground hover:bg-primary/90",
+  destructive: "bg-destructive text-primary-foreground hover:bg-destructive/90",
+  outline: "border border-border bg-transparent text-foreground hover:bg-muted",
+  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+  ghost: "text-foreground hover:bg-muted",
+  link: "text-primary underline-offset-4 hover:underline",
+};
+
+const buttonSizes: Record<ButtonSize, string> = {
+  default: "h-10 px-4 py-2",
+  sm: "h-9 px-3",
+  lg: "h-11 px-8",
+  icon: "size-10",
+};
+
+function Button({ variant = "default", size = "default", className, type = "button", ...rest }: ButtonProps) {
+  return (
+    <button
+      type={type}
+      className={classNames(
+        "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50",
+        buttonVariants[variant],
+        buttonSizes[size],
+        className,
+      )}
+      {...rest}
+    />
+  );
+}
+
+// ---------------------------------------------------------------------------
+// Orb — documented props from https://ui.elevenlabs.io/docs/components/orb
+// ---------------------------------------------------------------------------
+
+type AgentState = null | "thinking" | "listening" | "talking";
+
+type OrbProps = {
+  colors?: [string, string];
+  colorsRef?: React.RefObject<[string, string]>;
+  resizeDebounce?: number;
+  seed?: number;
+  agentState?: AgentState;
+  volumeMode?: "auto" | "manual";
+  manualInput?: number;
+  manualOutput?: number;
+  inputVolumeRef?: React.RefObject<number>;
+  outputVolumeRef?: React.RefObject<number>;
+  getInputVolume?: () => number;
+  getOutputVolume?: () => number;
+  className?: string;
+};
+
+function orbVolume(
+  mode: "auto" | "manual",
+  manual: number | undefined,
+  ref: React.RefObject<number> | undefined,
+  read: (() => number) | undefined,
+): number {
+  const raw = mode === "manual" ? manual : (read?.() ?? ref?.current ?? 0);
+  return Math.max(Math.min(raw ?? 0, 1), 0);
+}
+
+/**
+ * The documented orb is a Three.js mesh with per-frame shader animation; the
+ * preview renders one deterministic canvas frame of the same state: the two
+ * stop gradient, the audio-volume swell, and the agent-state ring.
+ */
+function Orb({
+  colors = ["#CADCFC", "#A0B9D1"],
+  colorsRef,
+  resizeDebounce = 100,
+  seed = 1,
+  agentState = null,
+  volumeMode = "auto",
+  manualInput,
+  manualOutput,
+  inputVolumeRef,
+  outputVolumeRef,
+  getInputVolume,
+  getOutputVolume,
+  className,
+}: OrbProps) {
+  const canvasRef = React.useRef<HTMLCanvasElement | null>(null);
+  const [activeColors, setActiveColors] = React.useState<[string, string]>(colors);
+
+  React.useEffect(() => {
+    if (colorsRef?.current) setActiveColors(colorsRef.current);
+  }, [colorsRef]);
+
+  React.useEffect(() => {
+    const canvas = canvasRef.current;
+    if (!canvas) return;
+
+    const draw = () => {
+      const context = canvas.getContext("2d");
+      if (!context) return;
+      const ratio = window.devicePixelRatio || 1;
+      const width = canvas.clientWidth;
+      const height = canvas.clientHeight;
+      if (width === 0 || height === 0) return;
+      canvas.width = Math.floor(width * ratio);
+      canvas.height = Math.floor(height * ratio);
+      context.setTransform(ratio, 0, 0, ratio, 0, 0);
+      context.clearRect(0, 0, width, height);
+
+      const inputVolume = orbVolume(volumeMode, manualInput, inputVolumeRef, getInputVolume);
+      const outputVolume = orbVolume(volumeMode, manualOutput, outputVolumeRef, getOutputVolume);
+      const volume = Math.max(inputVolume, outputVolume);
+      const radius = Math.min(width, height) / 2 - 2 - volume * 3;
+      const cx = width / 2;
+      const cy = height / 2;
+
+      const gradient = context.createRadialGradient(
+        cx - radius * 0.35,
+        cy - radius * 0.4,
+        radius * 0.1,
+        cx,
+        cy,
+        radius,
+      );
+      gradient.addColorStop(0, activeColors[0]);
+      gradient.addColorStop(1, activeColors[1]);
+      context.fillStyle = gradient;
+      context.beginPath();
+      context.arc(cx, cy, radius, 0, Math.PI * 2);
+      context.fill();
+
+      const random = seededRandom(seed);
+      context.globalAlpha = 0.18;
+      context.strokeStyle = activeColors[0];
+      for (let band = 0; band < 4; band += 1) {
+        const base = radius * (0.28 + band * 0.16);
+        context.lineWidth = Math.max(radius * 0.05, 1.5);
+        context.beginPath();
+        for (let step = 0; step <= 48; step += 1) {
+          const angle = (step / 48) * Math.PI * 2;
+          const swell = (random() - 0.5) * radius * 0.06 + volume * radius * 0.06;
+          const r = base + swell * Math.sin(angle * (band + 2));
+          const x = cx + Math.cos(angle) * r;
+          const y = cy + Math.sin(angle) * r;
+          if (step === 0) context.moveTo(x, y);
+          else context.lineTo(x, y);
+        }
+        context.closePath();
+        context.stroke();
+      }
+      context.globalAlpha = 1;
+
+      if (agentState !== null) {
+        context.strokeStyle = activeColors[0];
+        context.lineWidth = Math.max(radius * 0.045, 1.5);
+        if (agentState === "thinking") context.setLineDash([radius * 0.18, radius * 0.14]);
+        context.beginPath();
+        context.arc(cx, cy, radius + Math.max(radius * 0.12, 4), 0, Math.PI * 2);
+        context.stroke();
+        context.setLineDash([]);
+      }
+    };
+
+    draw();
+    let timer: number | undefined;
+    const observer = new ResizeObserver(() => {
+      window.clearTimeout(timer);
+      timer = window.setTimeout(draw, resizeDebounce);
+    });
+    observer.observe(canvas);
+    return () => {
+      window.clearTimeout(timer);
+      observer.disconnect();
+    };
+  }, [activeColors, agentState, getInputVolume, getOutputVolume, inputVolumeRef, manualInput, manualOutput, outputVolumeRef, resizeDebounce, seed, volumeMode]);
+
+  return <canvas ref={canvasRef} className={classNames("block h-full w-full", className)} aria-hidden="true" />;
+}
+
+// ---------------------------------------------------------------------------
+// Waveform family — documented props from https://ui.elevenlabs.io/docs/components/waveform
+// ---------------------------------------------------------------------------
+
+type WaveformProps = {
+  data: number[];
+  barWidth?: number;
+  barHeight?: number;
+  barGap?: number;
+  barRadius?: number;
+  barColor?: string;
+  fadeEdges?: boolean;
+  fadeWidth?: number;
+  height?: string | number;
+  onBarClick?: (index: number, value: number) => void;
+};
+
+function Waveform({
+  data,
+  barWidth = 4,
+  barHeight = 4,
+  barGap = 2,
+  barRadius = 2,
+  barColor,
+  fadeEdges = true,
+  fadeWidth = 24,
+  height = 128,
+  onBarClick,
+}: WaveformProps) {
+  const canvasRef = React.useRef<HTMLCanvasElement | null>(null);
+
+  React.useEffect(() => {
+    const canvas = canvasRef.current;
+    if (!canvas) return;
+    drawWaveformFrame(canvas, data, {
+      barWidth,
+      barHeight,
+      barGap,
+      barRadius,
+      barColor: resolveBarColor(barColor),
+      fadeEdges,
+      fadeWidth,
+    });
+    const observer = new ResizeObserver(() =>
+      drawWaveformFrame(canvas, data, {
+        barWidth,
+        barHeight,
+        barGap,
+        barRadius,
+        barColor: resolveBarColor(barColor),
+        fadeEdges,
+        fadeWidth,
+      }),
+    );
+    observer.observe(canvas);
+    return () => observer.disconnect();
+  }, [barColor, barGap, barHeight, barRadius, barWidth, data, fadeEdges, fadeWidth]);
+
+  const handleClick = (event: React.MouseEvent<HTMLCanvasElement>) => {
+    if (!onBarClick) return;
+    const canvas = canvasRef.current;
+    if (!canvas) return;
+    const rect = canvas.getBoundingClientRect();
+    const slot = barWidth + barGap;
+    const index = Math.max(Math.floor((event.clientX - rect.left) / slot), 0);
+    if (index < data.length) onBarClick(index, data[index]);
+  };
+
+  return <canvas ref={canvasRef} onClick={handleClick} className="block w-full" style={{ height }} />;
+}
+
+type LiveWaveformProps = React.ComponentPropsWithoutRef<"div"> & {
+  active?: boolean;
+  processing?: boolean;
+  barWidth?: number;
+  barHeight?: number;
+  barGap?: number;
+  barRadius?: number;
+  barColor?: string;
+  fadeEdges?: boolean;
+  fadeWidth?: number;
+  height?: string | number;
+  sensitivity?: number;
+  smoothingTimeConstant?: number;
+  fftSize?: number;
+  historySize?: number;
+  updateRate?: number;
+  mode?: "scrolling" | "static";
+  onError?: (error: Error) => void;
+  onStreamReady?: (stream: MediaStream) => void;
+  onStreamEnd?: () => void;
+};
+
+/**
+ * The documented live visualizer draws three states (idle, processing wave,
+ * active microphone spectrum). The preview renders one seeded frame per state;
+ * the real component animates these frames with requestAnimationFrame.
+ */
+function LiveWaveform({
+  active = false,
+  processing = false,
+  barWidth = 3,
+  barHeight = 4,
+  barGap = 1,
+  barRadius = 1.5,
+  barColor,
+  fadeEdges = true,
+  fadeWidth = 24,
+  height = 64,
+  sensitivity = 1,
+  smoothingTimeConstant: _smoothingTimeConstant = 0.8,
+  fftSize: _fftSize = 256,
+  historySize = 60,
+  updateRate: _updateRate = 30,
+  mode = "static",
+  onError: _onError,
+  onStreamReady: _onStreamReady,
+  onStreamEnd: _onStreamEnd,
+  className,
+  ...rest
+}: LiveWaveformProps) {
+  const canvasRef = React.useRef<HTMLCanvasElement | null>(null);
+
+  React.useEffect(() => {
+    const canvas = canvasRef.current;
+    if (!canvas) return;
+    const random = seededRandom(historySize + (active ? 7 : processing ? 3 : 1));
+
+    const values: number[] = [];
+    const count = Math.max(Math.ceil(canvas.clientWidth / (barWidth + barGap)), 8);
+    for (let index = 0; index < count; index += 1) {
+      const position = index / Math.max(count - 1, 1);
+      if (active) {
+        const spectrum = Math.sin(position * Math.PI) * (0.55 + random() * 0.45);
+        values.push(Math.max(Math.min(spectrum * sensitivity, 1), 0.04));
+      } else if (processing) {
+        const wave = (Math.sin(position * Math.PI * 3) + 1) / 2;
+        values.push(0.12 + wave * 0.5);
+      } else {
+        values.push(mode === "scrolling" ? 0.08 + random() * 0.06 : 0.1);
+      }
+    }
+
+    drawWaveformFrame(canvas, values, {
+      barWidth,
+      barHeight,
+      barGap,
+      barRadius,
+      barColor: resolveBarColor(barColor),
+      fadeEdges,
+      fadeWidth,
+    });
+  }, [active, barColor, barGap, barHeight, barRadius, barWidth, fadeEdges, fadeWidth, historySize, mode, processing, sensitivity]);
+
+  return (
+    <div className={classNames("w-full", className)} {...rest}>
+      <canvas ref={canvasRef} className="block w-full" style={{ height }} aria-hidden="true" />
+    </div>
+  );
+}
+
+// ---------------------------------------------------------------------------
+// Audio Player — documented API from https://ui.elevenlabs.io/docs/components/audio-player
+// ---------------------------------------------------------------------------
+
+interface AudioPlayerItem<TData = unknown> {
+  id: string | number;
+  src: string;
+  data?: TData;
+}
+
+interface AudioPlayerState<TData> {
+  ref: React.RefObject<HTMLAudioElement | null>;
+  activeItem: AudioPlayerItem<TData> | null;
+  duration: number;
+  error: MediaError | null;
+  isPlaying: boolean;
+  isBuffering: boolean;
+  playbackRate: number;
+  isItemActive: (id: string | number) => boolean;
+  setActiveItem: (item: AudioPlayerItem<TData> | null) => void;
+  play: (item?: AudioPlayerItem<TData>) => void;
+  pause: () => void;
+  seek: (time: number) => void;
+  setPlaybackRate: (rate: number) => void;
+}
+
+/** The documented surface plus the timeline clock `useAudioPlayerTime` reads. */
+type AudioPlayerContextValue<TData> = AudioPlayerState<TData> & { time: number };
+
+const AudioPlayerContext = React.createContext<AudioPlayerContextValue<unknown> | null>(null);
+
+/** The item's free-form `data` bag may declare a demo duration in seconds. */
+function declaredDuration(item: AudioPlayerItem<unknown> | null): number {
+  const data: unknown = item?.data;
+  if (typeof data === "object" && data !== null && "durationSeconds" in data) {
+    const value = (data as { durationSeconds?: unknown }).durationSeconds;
+    if (typeof value === "number" && Number.isFinite(value)) return value;
+  }
+  return 0;
+}
+
+/**
+ * Owns playback state for every audio part below. The preview has no bundled
+ * media, so the timeline runs from the duration the item declares and the
+ * `<audio>` element stays source-less; every documented hook and component
+ * behaves as documented on top of it.
+ */
+function AudioPlayerProvider<TData = unknown>({ children }: { children: React.ReactNode }) {
+  const audioRef = React.useRef<HTMLAudioElement | null>(null);
+  const [activeItem, setActiveItemState] = React.useState<AudioPlayerItem<TData> | null>(null);
+  const [time, setTime] = React.useState(0);
+  const [isPlaying, setIsPlaying] = React.useState(false);
+  const [playbackRate, setPlaybackRateState] = React.useState(1);
+
+  const duration = declaredDuration(activeItem as AudioPlayerItem<unknown> | null);
+
+  React.useEffect(() => {
+    if (!isPlaying) return;
+    const timer = window.setInterval(() => {
+      setTime((previous) => {
+        const next = previous + 0.25 * playbackRate;
+        if (next >= duration) {
+          setIsPlaying(false);
+          return duration;
+        }
+        return next;
+      });
+    }, 250);
+    return () => window.clearInterval(timer);
+  }, [duration, isPlaying, playbackRate]);
+
+  const value = React.useMemo<AudioPlayerContextValue<TData>>(
+    () => ({
+      ref: audioRef,
+      activeItem,
+      duration,
+      error: null,
+      isPlaying,
+      isBuffering: false,
+      playbackRate,
+      time,
+      isItemActive: (id) => activeItem?.id === id,
+      setActiveItem: (item) => {
+        setActiveItemState(item);
+        setTime(0);
+      },
+      play: (item) => {
+        if (item) {
+          setActiveItemState(item);
+          setTime(0);
+        }
+        setIsPlaying(true);
+      },
+      pause: () => setIsPlaying(false),
+      seek: (next) => setTime(Math.max(Math.min(next, duration > 0 ? duration : next), 0)),
+      setPlaybackRate: setPlaybackRateState,
+    }),
+    [activeItem, duration, isPlaying, playbackRate, time],
+  );
+
+  return (
+    <AudioPlayerContext.Provider value={value as AudioPlayerContextValue<unknown>}>
+      <audio ref={audioRef} />
+      {children}
+    </AudioPlayerContext.Provider>
+  );
+}
+
+function useAudioPlayer<TData = unknown>(): AudioPlayerState<TData> {
+  const context = React.useContext(AudioPlayerContext);
+  if (!context) throw new Error("useAudioPlayer must be used within AudioPlayerProvider");
+  return context as AudioPlayerContextValue<TData>;
+}
+
+/** Documented to follow playback time; reads the provider's timeline clock. */
+function useAudioPlayerTime(): number {
+  const context = React.useContext(AudioPlayerContext);
+  if (!context) throw new Error("useAudioPlayerTime must be used within AudioPlayerProvider");
+  return context.time;
+}
+
+function AudioPlayerButton({ item, variant = "outline", size = "default", ...rest }: { item?: AudioPlayerItem } & ButtonProps) {
+  const player = useAudioPlayer();
+  const isActive = item ? player.isItemActive(item.id) : player.activeItem !== null;
+  const isPlaying = isActive && player.isPlaying;
+  return (
+    <Button
+      variant={variant}
+      size={size}
+      aria-label={isPlaying ? "Pause" : "Play"}
+      onClick={() => (isPlaying ? player.pause() : player.play(item))}
+      {...rest}
+    >
+      {player.isBuffering ? (
+        <span className="size-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
+      ) : isPlaying ? (
+        <PauseIcon className="size-4" />
+      ) : (
+        <PlayIcon className="size-4" />
+      )}
+    </Button>
+  );
+}
+
+function AudioPlayerProgress({ className, ...rest }: Omit<React.ComponentPropsWithoutRef<typeof Slider.Root>, "min" | "max" | "value">) {
+  const player = useAudioPlayer();
+  const time = useAudioPlayerTime();
+  const percent = player.duration > 0 ? (time / player.duration) * 100 : 0;
+  return (
+    <Slider.Root
+      value={[percent]}
+      min={0}
+      max={100}
+      onValueChange={(next) => player.seek(((next[0] ?? 0) / 100) * player.duration)}
+      className={classNames("relative flex h-5 w-full touch-none select-none items-center", className)}
+      {...rest}
+    >
+      <Slider.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-muted">
+        <Slider.Range className="absolute h-full bg-primary" />
+      </Slider.Track>
+      <Slider.Thumb className="block size-4 rounded-full border border-primary bg-background" />
+    </Slider.Root>
+  );
+}
+
+function AudioPlayerTime({ className, ...rest }: React.ComponentPropsWithoutRef<"span">) {
+  const time = useAudioPlayerTime();
+  return (
+    <span className={className} {...rest}>
+      {formatTime(time)}
+    </span>
+  );
+}
+
+function AudioPlayerDuration({ className, ...rest }: React.ComponentPropsWithoutRef<"span">) {
+  const { duration } = useAudioPlayer();
+  return (
+    <span className={className} {...rest}>
+      {duration > 0 ? formatTime(duration) : "--:--"}
+    </span>
+  );
+}
+
+function speedLabel(rate: number): string {
+  return rate === 1 ? "Normal" : `${rate}x`;
+}
+
+function AudioPlayerSpeed({
+  speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],
+  variant = "ghost",
+  size = "icon",
+  ...rest
+}: { speeds?: readonly number[] } & ButtonProps) {
+  const player = useAudioPlayer();
+  return (
+    <DropdownMenu.Root>
+      <DropdownMenu.Trigger asChild>
+        <Button variant={variant} size={size} aria-label="Playback speed" {...rest}>
+          <Settings className="size-4" />
+        </Button>
+      </DropdownMenu.Trigger>
+      <DropdownMenu.Portal>
+        <DropdownMenu.Content className="min-w-[120px] rounded-md border border-border bg-popover p-1 text-popover-foreground">
+          {speeds.map((speed) => (
+            <DropdownMenu.Item
+              key={speed}
+              className="rounded-sm px-2 py-1.5 text-sm"
+              onSelect={() => player.setPlaybackRate(speed)}
+            >
+              {speedLabel(speed)}
+            </DropdownMenu.Item>
+          ))}
+        </DropdownMenu.Content>
+      </DropdownMenu.Portal>
+    </DropdownMenu.Root>
+  );
+}
+
+function AudioPlayerSpeedButtonGroup({
+  speeds = [0.5, 1, 1.5, 2],
+  className,
+  ...rest
+}: { speeds?: readonly number[] } & React.ComponentPropsWithoutRef<"div">) {
+  const player = useAudioPlayer();
+  return (
+    <div className={classNames("flex gap-1", className)} {...rest}>
+      {speeds.map((speed) => (
+        <Button
+          key={speed}
+          size="sm"
+          variant={player.playbackRate === speed ? "secondary" : "ghost"}
+          className="px-2 text-xs"
+          onClick={() => player.setPlaybackRate(speed)}
+        >
+          {speedLabel(speed)}
+        </Button>
+      ))}
+    </div>
+  );
+}
+
+// ---------------------------------------------------------------------------
+// Conversation — documented API from https://ui.elevenlabs.io/docs/components/conversation
+// ---------------------------------------------------------------------------
+
+type ConversationProps = React.ComponentPropsWithoutRef<"div"> & {
+  initial?: "smooth";
+  resize?: "smooth";
+};
+
+function Conversation({ className, initial: _initial = "smooth", resize: _resize = "smooth", ...rest }: ConversationProps) {
+  return <div className={classNames("relative flex-1 overflow-y-auto", className)} {...rest} />;
+}
+
+function ConversationContent({ className, ...rest }: React.ComponentPropsWithoutRef<"div">) {
+  return <div className={classNames("flex flex-col gap-4", className)} {...rest} />;
+}
+
+type ConversationEmptyStateProps = {
+  title?: string;
+  description?: string;
+  icon?: React.ReactNode;
+  className?: string;
+};
+
+function ConversationEmptyState({
+  title = "No messages yet",
+  description,
+  icon,
+  className,
+}: ConversationEmptyStateProps) {
+  return (
+    <div className={classNames("flex h-full flex-col items-center justify-center gap-3 text-center", className)}>
+      {icon}
+      <p className="text-sm font-medium">{title}</p>
+      {description ? <p className="text-sm text-muted-foreground">{description}</p> : null}
+    </div>
+  );
+}
+
+function ConversationScrollButton({ className, ...rest }: React.ComponentPropsWithoutRef<"button">) {
+  return (
+    <button
+      type="button"
+      aria-label="Scroll to latest message"
+      className={classNames(
+        "absolute bottom-3 right-3 flex size-9 items-center justify-center rounded-full border border-border bg-popover text-popover-foreground",
+        className,
+      )}
+      {...rest}
+    >
+      <ArrowDown className="size-4" />
+    </button>
+  );
+}
+
+// ---------------------------------------------------------------------------
+// Voice Button — documented API from https://ui.elevenlabs.io/docs/components/voice-button
+// ---------------------------------------------------------------------------
+
+type VoiceButtonState = "idle" | "recording" | "processing" | "success" | "error";
+
+type VoiceButtonProps = React.ComponentPropsWithoutRef<"button"> & {
+  state?: VoiceButtonState;
+  onPress?: () => void;
+  label?: React.ReactNode;
+  trailing?: React.ReactNode;
+  icon?: React.ReactNode;
+  variant?: ButtonVariant;
+  size?: ButtonSize;
+  waveformClassName?: string;
+  feedbackDuration?: number;
+};
+
+function VoiceButton({
+  state = "idle",
+  onPress,
+  label,
+  trailing,
+  icon,
+  variant = "outline",
+  size = "default",
+  className,
+  waveformClassName,
+  feedbackDuration: _feedbackDuration = 1500,
+  onClick,
+  ...rest
+}: VoiceButtonProps) {
+  return (
+    <Button
+      variant={variant}
+      size={size}
+      className={classNames("relative overflow-hidden", className)}
+      onClick={(event) => {
+        onPress?.();
+        onClick?.(event);
+      }}
+      {...rest}
+    >
+      {label ? <span>{label}</span> : null}
+      {state === "recording" ? (
+        <span className={classNames("flex h-full flex-1 items-center gap-0.5 px-1", waveformClassName)}>
+          {Array.from({ length: 16 }, (_, index) => (
+            <span
+              key={index}
+              className="w-0.5 rounded-full bg-current"
+              style={{ height: `${20 + Math.abs(Math.sin(index * 1.7)) * 55}%` }}
+            />
+          ))}
+        </span>
+      ) : null}
+      {state === "processing" ? <span className="size-4 rounded-full border-2 border-current border-t-transparent" /> : null}
+      {state === "success" ? <Check className="size-4" /> : null}
+      {state === "error" ? <X className="size-4" /> : null}
+      {state === "idle" && icon ? icon : state === "idle" && size === "icon" ? <MicIcon className="size-4" /> : null}
+      {trailing ? <span className="text-xs opacity-70">{trailing}</span> : null}
+    </Button>
+  );
+}
+
+// ---------------------------------------------------------------------------
+// Stories
+// ---------------------------------------------------------------------------
+
+const OrbStory: StoryRender = ({ sample }) => (
+  <div className="flex w-full max-w-xl flex-col items-center gap-6">
+    <div className="grid grid-cols-3 gap-6">
+      {(
+        [
+          ["#CADCFC", "#A0B9D1", null],
+          ["#F6E7D8", "#E0CFC2", "listening"],
+          ["#E5E7EB", "#9CA3AF", "talking"],
+        ] as Array<[string, string, AgentState]>
+      ).map(([from, to, state], index) => (
+        <div key={from} className="bg-muted size-32 rounded-full p-1 shadow-[inset_0_2px_8px_rgba(0,0,0,0.5)]">
+          <div className="bg-background size-full overflow-hidden rounded-full shadow-[inset_0_0_12px_rgba(0,0,0,0.3)]">
+            <Orb colors={[from, to]} seed={(index + 1) * 1000} agentState={state} />
+          </div>
+        </div>
+      ))}
+    </div>
+    <div className="text-center">
+      <p className="text-lg font-semibold">{sample.headline ?? "Meet Nova, the voice agent that never sleeps"}</p>
+      <p className="mt-1 text-sm text-muted-foreground">
+        {sample.support ?? "Idle, listening, and talking states — rendered from one seeded frame."}
+      </p>
+    </div>
+  </div>
+);
+
+interface TrackData {
+  title: string;
+  artist: string;
+  durationSeconds: number;
+}
+
+type Track = AudioPlayerItem<TrackData>;
+
+const podcastTracks: Track[] = [
+  { id: "ep-48", src: "/audio/episode-48.mp3", data: { title: "Ep. 48 — Shipping voice agents", artist: "Signal & Noise", durationSeconds: 227 } },
+  { id: "ep-47", src: "/audio/episode-47.mp3", data: { title: "Ep. 47 — Latency is a feature", artist: "Signal & Noise", durationSeconds: 312 } },
+  { id: "ep-46", src: "/audio/episode-46.mp3", data: { title: "Ep. 46 — Designing for interruption", artist: "Signal & Noise", durationSeconds: 186 } },
+];
+
+function AudioPlayerPanel({ sample }: { sample: Readonly<Record<string, string>> }) {
+  const player = useAudioPlayer<TrackData>();
+  React.useEffect(() => {
+    // Seed the featured episode and a mid-track pause so the static preview
+    // shows the player the way it looks in use, via the documented hooks only.
+    player.setActiveItem(podcastTracks[0]);
+    player.seek(84);
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, []);
+  const active = player.activeItem ?? podcastTracks[0];
+  return (
+    <div className="w-full max-w-xl overflow-hidden rounded-xl border border-border bg-card text-card-foreground">
+      <div className="flex items-center gap-4 p-6">
+        <AudioPlayerButton item={active} variant="outline" size="icon" className="shrink-0" />
+        <div className="min-w-0 flex-1">
+          <p className="truncate text-sm font-semibold">{sample.trackTitle ?? active.data?.title}</p>
+          <p className="truncate text-sm text-muted-foreground">{sample.trackArtist ?? active.data?.artist}</p>
+        </div>
+        <AudioPlayerSpeed variant="ghost" size="icon" />
+      </div>
+      <div className="flex items-center gap-3 px-6">
+        <AudioPlayerTime className="text-xs tabular-nums text-muted-foreground" />
+        <AudioPlayerProgress className="flex-1" />
+        <AudioPlayerDuration className="text-xs tabular-nums text-muted-foreground" />
+      </div>
+      <div className="mt-4 grid gap-1 border-t border-border p-3">
+        {podcastTracks.map((track, index) => (
+          <button
+            key={track.id}
+            type="button"
+            className={classNames(
+              "flex items-center gap-3 rounded-md px-2 py-1.5 text-left text-sm",
+              player.isItemActive(track.id) ? "bg-secondary" : "hover:bg-muted",
+            )}
+            onClick={() => player.setActiveItem(track)}
+          >
+            <span className="w-5 shrink-0 text-center text-xs tabular-nums text-muted-foreground">{index + 1}</span>
+            <span className="truncate">{track.data?.title}</span>
+            <span className="ml-auto shrink-0 text-xs tabular-nums text-muted-foreground">
+              {formatTime(track.data?.durationSeconds ?? 0)}
+            </span>
+          </button>
+        ))}
+      </div>
+    </div>
+  );
+}
+
+const AudioPlayerStory: StoryRender = ({ sample }) => (
+  <AudioPlayerProvider>
+    <AudioPlayerPanel sample={sample} />
+  </AudioPlayerProvider>
+);
+
+const WaveformData = Array.from({ length: 72 }, (_, index) => {
+  const envelope = Math.sin((index / 71) * Math.PI) * 0.65 + 0.25;
+  return Math.max(Math.min(envelope + Math.sin(index * 1.31) * 0.12, 1), 0.06);
+});
+
+const WaveformStory: StoryRender = ({ sample }) => (
+  <div className="w-full max-w-md rounded-xl border border-border bg-card p-5 text-card-foreground">
+    <div className="mb-3 flex items-baseline justify-between">
+      <p className="text-sm font-semibold">{sample.title ?? "Field notes — voice memo"}</p>
+      <p className="text-xs tabular-nums text-muted-foreground">{sample.duration ?? "1:12 recorded"}</p>
+    </div>
+    <Waveform data={WaveformData} height={96} barWidth={4} barGap={2} barColor="var(--muted-foreground)" />
+    <p className="mt-3 text-xs text-muted-foreground">
+      {sample.caption ?? "Tap a bar to jump to that moment in the recording."}
+    </p>
+  </div>
+);
+
+const LiveWaveformStory: StoryRender = ({ sample }) => (
+  <div className="w-full max-w-lg rounded-xl border border-border bg-card p-6 text-card-foreground">
+    <p className="text-base font-semibold">{sample.title ?? "Transcribing your note"}</p>
+    <p className="mt-1 text-sm text-muted-foreground">
+      {sample.caption ?? "Processing the recording — the waveform shows the incoming audio."}
+    </p>
+    <div className="mt-5">
+      <LiveWaveform processing mode="static" height={80} barWidth={3} barGap={2} barColor="var(--muted-foreground)" />
+    </div>
+    <div className="mt-5 flex flex-wrap gap-2">
+      <Button size="sm" variant="outline">
+        Cancel
+      </Button>
+      <Button size="sm" variant="secondary">
+        Keep listening
+      </Button>
+    </div>
+  </div>
+);
+
+interface ChatMessage {
+  id: string;
+  from: "user" | "assistant";
+  text: string;
+}
+
+const ConversationStory: StoryRender = ({ sample }) => {
+  const messages: ChatMessage[] = [
+    { id: "1", from: "user", text: sample.question1 ?? "Hey, I need help with my order." },
+    {
+      id: "2",
+      from: "assistant",
+      text: sample.answer1 ?? "Happy to help. Could you share your order number?",
+    },
+    { id: "3", from: "user", text: sample.question2 ?? "It's ORDER-12345." },
+    {
+      id: "4",
+      from: "assistant",
+      text:
+        sample.answer2 ??
+        "Thanks! Order ORDER-12345 was placed on March 15th and ships within 1–2 business days. Want to change anything before it leaves?",
+    },
+  ];
+  return (
+    <div className="relative h-[360px] w-full max-w-md rounded-xl border border-border bg-card text-card-foreground">
+      <div className="flex h-full flex-col">
+        <Conversation className="p-4">
+          <ConversationContent>
+            {messages.map((message) => (
+              <div key={message.id} className={classNames("flex", message.from === "user" ? "justify-end" : "justify-start")}>
+                <div
+                  className={classNames(
+                    "max-w-[80%] rounded-2xl px-4 py-2 text-sm",
+                    message.from === "user" ? "bg-secondary text-secondary-foreground" : "border border-border text-foreground",
+                  )}
+                >
+                  {message.text}
+                </div>
+              </div>
+            ))}
+          </ConversationContent>
+        </Conversation>
+        <ConversationScrollButton />
+      </div>
+    </div>
+  );
+};
+
+const VoiceButtonStory: StoryRender = ({ sample }) => (
+  <div className="flex min-h-[160px] w-full max-w-md flex-col items-center justify-center gap-4">
+    <VoiceButton
+      state="recording"
+      label={sample.label ?? "Push to talk"}
+      trailing={sample.trailing ?? "⌥Space"}
+      className="min-w-[220px]"
+    />
+    <p className="text-sm text-muted-foreground">
+      {sample.caption ?? "Release to send — the agent replies in about a second."}
+    </p>
+  </div>
+);
+
+export const storyRenders: Record<string, StoryRender> = {
+  "elevenlabs-ui/Orb": OrbStory,
+  "elevenlabs-ui/Audio Player": AudioPlayerStory,
+  "elevenlabs-ui/Waveform": WaveformStory,
+  "elevenlabs-ui/Live Waveform": LiveWaveformStory,
+  "elevenlabs-ui/Conversation": ConversationStory,
+  "elevenlabs-ui/Voice Button": VoiceButtonStory,
+};
```

[Next page](https://git.cafe/screen/col/pull/66/diffs?after=ImNvbXBvbmVudHMvcHJldmlldy1zdG9yaWVzL2VsZXZlbmxhYnMtdWkudHN4Ig&expectedVersion=1&format=markdown)
