Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: add demo gallery as a prompt collection drawer
Restore and simplify the demo gallery components. Cards are now compact
clickable prompts in a single-column list instead of a grid with large
preview areas. Clicking a card sends its prompt to the chat.
  • Loading branch information
GeneralJerel committed Mar 26, 2026
commit 501f83ce4086c5006cf94e7af754bc0844946d6c
63 changes: 63 additions & 0 deletions apps/app/src/components/demo-gallery/demo-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use client";

import type { DemoItem } from "./demo-data";

const CATEGORY_COLORS: Record<string, { bg: string; text: string }> = {
"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 (
<button
onClick={() => onTry(demo)}
className="rounded-xl overflow-hidden flex flex-col text-left transition-all duration-200 hover:shadow-lg hover:-translate-y-0.5 cursor-pointer w-full"
style={{
border: "1px solid var(--color-border-glass, rgba(0,0,0,0.1))",
background: "var(--surface-primary, #fff)",
}}
>
<div className="flex flex-col gap-1.5 p-4 flex-1">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-lg">{demo.emoji}</span>
<h3
className="text-sm font-semibold truncate"
style={{ color: "var(--text-primary, #1a1a1a)" }}
>
{demo.title}
</h3>
</div>
<span
className="text-[10px] font-semibold px-2 py-0.5 rounded-full shrink-0"
style={{
background: categoryColor.bg,
color: categoryColor.text,
}}
>
{demo.category}
</span>
</div>
<p
className="text-xs line-clamp-2"
style={{ color: "var(--text-secondary, #666)" }}
>
{demo.description}
</p>
</div>
</button>
);
}
126 changes: 126 additions & 0 deletions apps/app/src/components/demo-gallery/demo-data.ts
Original file line number Diff line number Diff line change
@@ -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",
];
139 changes: 139 additions & 0 deletions apps/app/src/components/demo-gallery/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"use client";

import { useState } from "react";
import { DEMO_EXAMPLES, type DemoCategory, type DemoItem } from "./demo-data";
import { DemoCard } from "./demo-card";
import { CategoryFilter } from "./category-filter";

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<DemoCategory | null>(null);

const filtered = selectedCategory
? DEMO_EXAMPLES.filter((d) => d.category === selectedCategory)
: DEMO_EXAMPLES;

return (
<>
{/* Backdrop */}
{open && (
<div
className="fixed inset-0 z-40"
style={{ background: "rgba(0,0,0,0.3)", backdropFilter: "blur(2px)" }}
onClick={onClose}
/>
)}

{/* Drawer panel */}
<div
className="fixed top-0 right-0 h-full z-50 flex flex-col transition-transform duration-300 ease-in-out"
style={{
width: 480,
maxWidth: "90vw",
transform: open ? "translateX(0)" : "translateX(100%)",
background: "var(--surface-primary, #fff)",
borderLeft: "1px solid var(--color-border-glass, rgba(0,0,0,0.1))",
boxShadow: open ? "-8px 0 30px rgba(0,0,0,0.1)" : "none",
}}
>
{/* Header */}
<div
className="flex items-center justify-between px-5 py-4 shrink-0"
style={{
borderBottom:
"1px solid var(--color-border-glass, rgba(0,0,0,0.1))",
}}
>
<div className="flex items-center gap-2">
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
style={{ color: "var(--text-secondary, #666)" }}
>
<rect width="7" height="7" x="3" y="3" rx="1" />
<rect width="7" height="7" x="14" y="3" rx="1" />
<rect width="7" height="7" x="14" y="14" rx="1" />
<rect width="7" height="7" x="3" y="14" rx="1" />
</svg>
<h2
className="text-base font-semibold"
style={{ color: "var(--text-primary, #1a1a1a)" }}
>
Demo Gallery
</h2>
<span
className="text-xs font-medium px-2 py-0.5 rounded-full"
style={{
background: "var(--color-background-secondary, #f5f5f5)",
color: "var(--text-secondary, #666)",
}}
>
{DEMO_EXAMPLES.length}
</span>
</div>
<button
onClick={onClose}
className="p-1.5 rounded-lg transition-colors duration-150 cursor-pointer"
style={{ color: "var(--text-secondary, #666)" }}
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
</div>

{/* Category filter */}
<div className="px-5 pt-4 pb-2 shrink-0">
<CategoryFilter
selected={selectedCategory}
onSelect={setSelectedCategory}
/>
</div>

{/* Card list */}
<div className="flex-1 overflow-y-auto px-5 pb-5">
<div className="flex flex-col gap-3 pt-2">
{filtered.map((demo) => (
<DemoCard key={demo.id} demo={demo} onTry={onTryDemo} />
))}
</div>

{filtered.length === 0 && (
<div className="flex flex-col items-center justify-center py-16 gap-2">
<p
className="text-sm font-medium"
style={{ color: "var(--text-secondary, #666)" }}
>
No demos in this category
</p>
</div>
)}
</div>
</div>
</>
);
}