diff --git a/apps/app/src/app/page.tsx b/apps/app/src/app/page.tsx
index 6871fff..1f1fa66 100644
--- a/apps/app/src/app/page.tsx
+++ b/apps/app/src/app/page.tsx
@@ -1,15 +1,27 @@
"use client";
-import { useEffect } from "react";
+import { useEffect, useState } from "react";
import { ExampleLayout } from "@/components/example-layout";
import { useGenerativeUIExamples, useExampleSuggestions } from "@/hooks";
import { ExplainerCardsPortal } from "@/components/explainer-cards";
-import { CopilotChat } from "@copilotkit/react-core/v2";
+import { DemoGallery, type DemoItem } from "@/components/demo-gallery";
+import { GridIcon } from "@/components/demo-gallery/grid-icon";
+import { CopilotChat, useAgent, useCopilotKit } from "@copilotkit/react-core/v2";
export default function HomePage() {
useGenerativeUIExamples();
useExampleSuggestions();
+ const [demoDrawerOpen, setDemoDrawerOpen] = useState(false);
+ const { agent } = useAgent();
+ const { copilotkit } = useCopilotKit();
+
+ const handleTryDemo = (demo: DemoItem) => {
+ setDemoDrawerOpen(false);
+ agent.addMessage({ id: crypto.randomUUID(), content: demo.prompt, role: "user" });
+ copilotkit.runAgent({ agent });
+ };
+
// Widget bridge: handle messages from widget iframes
useEffect(() => {
const handler = (e: MessageEvent) => {
@@ -55,6 +67,20 @@ export default function HomePage() {
+
+ setDemoDrawerOpen(false)}
+ onTryDemo={handleTryDemo}
+ />
>
);
}
diff --git a/apps/app/src/components/demo-gallery/category-filter.tsx b/apps/app/src/components/demo-gallery/category-filter.tsx
new file mode 100644
index 0000000..3d09f09
--- /dev/null
+++ b/apps/app/src/components/demo-gallery/category-filter.tsx
@@ -0,0 +1,39 @@
+"use client";
+
+import type { DemoCategory } from "./demo-data";
+import { DEMO_CATEGORIES } from "./demo-data";
+
+interface CategoryFilterProps {
+ selected: DemoCategory | null;
+ onSelect: (category: DemoCategory | null) => void;
+}
+
+export function CategoryFilter({ selected, onSelect }: CategoryFilterProps) {
+ const categories: (DemoCategory | null)[] = [null, ...DEMO_CATEGORIES];
+
+ return (
+
+ {categories.map((cat) => {
+ const isActive = cat === selected;
+ return (
+
+ );
+ })}
+
+ );
+}
diff --git a/apps/app/src/components/demo-gallery/demo-card.tsx b/apps/app/src/components/demo-gallery/demo-card.tsx
new file mode 100644
index 0000000..327a650
--- /dev/null
+++ b/apps/app/src/components/demo-gallery/demo-card.tsx
@@ -0,0 +1,63 @@
+"use client";
+
+import type { DemoItem } from "./demo-data";
+
+const CATEGORY_COLORS: Record = {
+ "3D / Animation": { bg: "rgba(139,92,246,0.12)", text: "rgba(139,92,246,1)" },
+ "Data Visualization": { bg: "rgba(59,130,246,0.12)", text: "rgba(59,130,246,1)" },
+ Diagrams: { bg: "rgba(16,185,129,0.12)", text: "rgba(16,185,129,1)" },
+ Interactive: { bg: "rgba(245,158,11,0.12)", text: "rgba(245,158,11,1)" },
+ "UI Components": { bg: "rgba(236,72,153,0.12)", text: "rgba(236,72,153,1)" },
+};
+
+interface DemoCardProps {
+ demo: DemoItem;
+ onTry: (demo: DemoItem) => void;
+}
+
+export function DemoCard({ demo, onTry }: DemoCardProps) {
+ const categoryColor = CATEGORY_COLORS[demo.category] ?? {
+ bg: "rgba(100,100,100,0.12)",
+ text: "rgba(100,100,100,1)",
+ };
+
+ return (
+
+ );
+}
diff --git a/apps/app/src/components/demo-gallery/demo-data.ts b/apps/app/src/components/demo-gallery/demo-data.ts
new file mode 100644
index 0000000..4ec304d
--- /dev/null
+++ b/apps/app/src/components/demo-gallery/demo-data.ts
@@ -0,0 +1,126 @@
+export type DemoCategory =
+ | "3D / Animation"
+ | "Data Visualization"
+ | "Diagrams"
+ | "Interactive"
+ | "UI Components";
+
+export interface DemoItem {
+ id: string;
+ title: string;
+ description: string;
+ category: DemoCategory;
+ emoji: string;
+ prompt: string;
+}
+
+export const DEMO_EXAMPLES: DemoItem[] = [
+ {
+ id: "demo-pitch-roll-yaw",
+ title: "Pitch, Roll & Yaw",
+ description:
+ "Interactive 3D airplane explaining pitch, roll, and yaw with control buttons",
+ category: "3D / Animation",
+ emoji: "โ๏ธ",
+ prompt:
+ "Create a 3D plane in Three.js to explain how pitch, roll, and yaw work. Give me buttons to control each axis. Add labels showing which rotation is which.",
+ },
+ {
+ id: "demo-weather",
+ title: "Weather Card",
+ description:
+ "Current weather conditions with temperature, humidity, wind, and UV index",
+ category: "UI Components",
+ emoji: "๐ค๏ธ",
+ prompt:
+ "Create a beautiful weather card showing current conditions for San Francisco with temperature, humidity, wind speed, UV index, and a 5-day mini forecast.",
+ },
+ {
+ id: "demo-binary-search",
+ title: "Binary Search",
+ description:
+ "Step-by-step visualization of binary search on a sorted array",
+ category: "Diagrams",
+ emoji: "๐",
+ prompt:
+ "Visualize how binary search works on a sorted list. Step by step with animation. Show the high, low, and mid pointers moving.",
+ },
+ {
+ id: "demo-solar-system",
+ title: "Solar System",
+ description:
+ "3D solar system with orbiting planets you can click for facts",
+ category: "3D / Animation",
+ emoji: "๐ช",
+ prompt:
+ "Build a 3D solar system with orbiting planets using Three.js. Let me click on each planet to see facts about it. Include realistic relative sizes and orbital speeds.",
+ },
+ {
+ id: "demo-dashboard",
+ title: "KPI Dashboard",
+ description:
+ "Quarterly performance dashboard with metrics cards and bar chart",
+ category: "Data Visualization",
+ emoji: "๐",
+ prompt:
+ "Create a KPI dashboard showing Q1 2026 performance with revenue, active users, and conversion rate. Include a monthly revenue bar chart and trend indicators.",
+ },
+ {
+ id: "demo-sorting",
+ title: "Sorting Comparison",
+ description:
+ "Animated side-by-side comparison of bubble sort vs quicksort",
+ category: "Diagrams",
+ emoji: "๐ถ",
+ prompt:
+ "Create an animated comparison of bubble sort vs quicksort running side by side on the same random array. Add speed controls and a step counter.",
+ },
+ {
+ id: "demo-pomodoro",
+ title: "Pomodoro Timer",
+ description:
+ "Focus timer with circular progress ring, session counter, and controls",
+ category: "Interactive",
+ emoji: "๐
",
+ prompt:
+ "Build a Pomodoro timer with a circular progress ring, start/pause/reset buttons, and a session counter. Use 25 min work / 5 min break intervals. Make it look clean and minimal.",
+ },
+ {
+ id: "demo-neural-network",
+ title: "Neural Network",
+ description:
+ "Interactive neural network diagram with animated forward pass",
+ category: "Diagrams",
+ emoji: "๐ง ",
+ prompt:
+ "Visualize a simple neural network with input, hidden, and output layers. Animate the forward pass showing data flowing through the network. Let me adjust the number of neurons per layer.",
+ },
+ {
+ id: "demo-invoice",
+ title: "Invoice Card",
+ description:
+ "Compact invoice card with amount, client info, and action buttons",
+ category: "UI Components",
+ emoji: "๐งพ",
+ prompt:
+ "Create an invoice card showing a monthly billing summary with client name, amount due, invoice number, and send/expand action buttons.",
+ },
+ {
+ id: "demo-music-visualizer",
+ title: "Music Equalizer",
+ description:
+ "Audio equalizer visualization with animated frequency bars and controls",
+ category: "3D / Animation",
+ emoji: "๐ต",
+ prompt:
+ "Create a music equalizer visualization with animated bars that respond to frequency sliders. Add controls for bass, mid, and treble. Use a gradient color scheme.",
+ },
+];
+
+export const DEMO_CATEGORIES: DemoCategory[] = [
+ "3D / Animation",
+ "Data Visualization",
+ "Diagrams",
+ "Interactive",
+ "UI Components",
+];
diff --git a/apps/app/src/components/demo-gallery/grid-icon.tsx b/apps/app/src/components/demo-gallery/grid-icon.tsx
new file mode 100644
index 0000000..450ab8d
--- /dev/null
+++ b/apps/app/src/components/demo-gallery/grid-icon.tsx
@@ -0,0 +1,27 @@
+import type { CSSProperties } from "react";
+
+interface GridIconProps {
+ size?: number;
+ style?: CSSProperties;
+}
+
+export function GridIcon({ size = 18, style }: GridIconProps) {
+ return (
+
+ );
+}
diff --git a/apps/app/src/components/demo-gallery/index.tsx b/apps/app/src/components/demo-gallery/index.tsx
new file mode 100644
index 0000000..e571b79
--- /dev/null
+++ b/apps/app/src/components/demo-gallery/index.tsx
@@ -0,0 +1,134 @@
+"use client";
+
+import { useState, useEffect } from "react";
+import { DEMO_EXAMPLES, type DemoCategory, type DemoItem } from "./demo-data";
+import { DemoCard } from "./demo-card";
+import { CategoryFilter } from "./category-filter";
+import { GridIcon } from "./grid-icon";
+
+export type { DemoItem } from "./demo-data";
+
+interface DemoGalleryProps {
+ open: boolean;
+ onClose: () => void;
+ onTryDemo: (demo: DemoItem) => void;
+}
+
+export function DemoGallery({ open, onClose, onTryDemo }: DemoGalleryProps) {
+ const [selectedCategory, setSelectedCategory] =
+ useState(null);
+
+ const filtered = selectedCategory
+ ? DEMO_EXAMPLES.filter((d) => d.category === selectedCategory)
+ : DEMO_EXAMPLES;
+
+ useEffect(() => {
+ if (!open) return;
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === "Escape") onClose();
+ };
+ document.addEventListener("keydown", handleKeyDown);
+ return () => document.removeEventListener("keydown", handleKeyDown);
+ }, [open, onClose]);
+
+ return (
+ <>
+ {/* Backdrop */}
+ {open && (
+
+ )}
+
+ {/* Drawer panel */}
+
+ {/* Header */}
+
+
+
+
+ Demo Gallery
+
+
+ {DEMO_EXAMPLES.length}
+
+
+
+
+
+ {/* Category filter */}
+
+
+
+
+ {/* Card list */}
+
+
+ {filtered.map((demo) => (
+
+ ))}
+
+
+ {filtered.length === 0 && (
+
+
+ No demos in this category
+
+
+ )}
+
+
+ >
+ );
+}