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 +

+
+ )} +
+
+ + ); +}