feat: design page scaffold previews with library toggle

Each surface now shows a realistic scaffold preview in a browser chrome
frame. Tab bar at the top toggles between library options (shadcn,
DaisyUI, HeroUI, Mantine, Aceternity, etc.) — the scaffold updates
instantly to show that library's visual language. Lock in confirms
the choice. Scaffolds cover all 6 surfaces × their library options.

Made-with: Cursor
This commit is contained in:
2026-03-02 12:47:10 -08:00
parent 7cf4f2ef78
commit 54248887f1
2 changed files with 734 additions and 121 deletions

View File

@@ -9,6 +9,7 @@ import {
Monitor, Globe, Settings, Smartphone, Mail, BookOpen,
Lock, CheckCircle2, Loader2, ChevronRight, Pencil,
} from "lucide-react";
import { SCAFFOLD_REGISTRY } from "@/components/design-scaffolds";
// ---------------------------------------------------------------------------
// Surface definitions
@@ -290,77 +291,7 @@ const ALL_SURFACES: Surface[] = [
];
// ---------------------------------------------------------------------------
// Theme option card
// ---------------------------------------------------------------------------
function ThemeCard({
theme,
selected,
locked,
onSelect,
}: {
theme: Theme;
selected: boolean;
locked: boolean;
onSelect: () => void;
}) {
return (
<button
onClick={onSelect}
disabled={locked && !selected}
className={cn(
"flex flex-col text-left rounded-xl border transition-all overflow-hidden",
"w-52 shrink-0",
selected && locked
? "border-foreground ring-2 ring-foreground shadow-sm"
: selected
? "border-foreground ring-1 ring-foreground"
: "border-border hover:border-foreground/40",
locked && !selected && "opacity-40 cursor-not-allowed"
)}
>
{/* Preview */}
<div className="h-28 p-2 bg-zinc-50 border-b border-border relative">
{theme.preview}
{selected && locked && (
<div className="absolute top-1.5 right-1.5 w-5 h-5 rounded-full bg-foreground flex items-center justify-center">
<Lock className="w-2.5 h-2.5 text-background" />
</div>
)}
{selected && !locked && (
<div className="absolute top-1.5 right-1.5 w-5 h-5 rounded-full bg-foreground flex items-center justify-center">
<CheckCircle2 className="w-2.5 h-2.5 text-background" />
</div>
)}
</div>
{/* Info */}
<div className="p-3 flex flex-col gap-1.5">
<div className="flex items-center justify-between gap-2">
<p className="text-xs font-semibold text-foreground">{theme.name}</p>
<a
href={theme.url}
target="_blank"
rel="noopener noreferrer"
onClick={e => e.stopPropagation()}
className="text-[10px] text-muted-foreground hover:text-foreground transition-colors shrink-0"
>
Docs
</a>
</div>
<p className="text-[11px] text-muted-foreground leading-relaxed">{theme.description}</p>
<div className="flex flex-wrap gap-1 mt-0.5">
{theme.tags.map(t => (
<Badge key={t} variant="secondary" className="text-[9px] px-1.5 py-0">{t}</Badge>
))}
</div>
</div>
</button>
);
}
// ---------------------------------------------------------------------------
// Surface section
// Surface section — tab toggle + scaffold preview + lock in
// ---------------------------------------------------------------------------
function SurfaceSection({
@@ -380,35 +311,56 @@ function SurfaceSection({
onUnlock: () => void;
saving: boolean;
}) {
const Icon = surface.icon;
const lockedTheme = surface.themes.find(t => t.id === lockedThemeId);
// Active preview tab — if locked show that, otherwise the selected/first
const previewId = lockedThemeId ?? selectedThemeId ?? surface.themes[0]?.id ?? null;
const activeTheme = surface.themes.find(t => t.id === previewId);
const ScaffoldComponent = previewId
? SCAFFOLD_REGISTRY[surface.id]?.[previewId]
: null;
return (
<div className="space-y-3">
{/* Surface header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2.5">
<div className="w-8 h-8 rounded-lg bg-muted flex items-center justify-center">
<Icon className="h-4 w-4 text-muted-foreground" />
</div>
<div>
<div className="flex items-center gap-2">
<p className="text-sm font-semibold">{surface.name}</p>
{lockedTheme && (
<Badge variant="secondary" className="text-[10px] gap-1 px-1.5">
<Lock className="w-2.5 h-2.5" />
{lockedTheme.name}
</Badge>
<div className="flex flex-col h-full gap-0">
{/* Tab bar */}
<div className="flex items-center gap-1 px-1 pb-3 flex-wrap">
{surface.themes.map(theme => {
const isActive = theme.id === previewId;
const isLocked = theme.id === lockedThemeId;
return (
<button
key={theme.id}
onClick={() => !lockedThemeId && onSelect(theme.id)}
disabled={!!lockedThemeId && !isLocked}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all border",
isActive && isLocked
? "bg-foreground text-background border-foreground"
: isActive
? "bg-foreground text-background border-foreground"
: lockedThemeId
? "opacity-30 border-transparent text-muted-foreground cursor-not-allowed"
: "border-border text-muted-foreground hover:border-foreground/40 hover:text-foreground"
)}
</div>
<p className="text-xs text-muted-foreground">{surface.description}</p>
</div>
</div>
>
{isLocked && <Lock className="w-2.5 h-2.5" />}
{theme.name}
</button>
);
})}
{/* Lock / Unlock controls */}
<div className="flex items-center gap-2 shrink-0">
{/* Spacer + actions */}
<div className="ml-auto flex items-center gap-2">
{activeTheme && (
<a
href={activeTheme.url}
target="_blank"
rel="noopener noreferrer"
className="text-[11px] text-muted-foreground hover:text-foreground transition-colors"
>
{activeTheme.name} docs
</a>
)}
{lockedThemeId ? (
<Button variant="outline" size="sm" onClick={onUnlock} className="gap-1.5 text-xs">
<Button variant="outline" size="sm" onClick={onUnlock} className="gap-1.5 text-xs h-7">
<Pencil className="h-3 w-3" />
Change
</Button>
@@ -417,7 +369,7 @@ function SurfaceSection({
size="sm"
onClick={onLock}
disabled={!selectedThemeId || saving}
className="gap-1.5 text-xs"
className="gap-1.5 text-xs h-7"
>
{saving ? <Loader2 className="h-3 w-3 animate-spin" /> : <Lock className="h-3 w-3" />}
Lock in
@@ -426,33 +378,41 @@ function SurfaceSection({
</div>
</div>
{/* Theme cards — horizontal scroll */}
{!lockedThemeId && (
<div className="flex gap-3 overflow-x-auto pb-2 -mx-1 px-1">
{surface.themes.map(theme => (
<ThemeCard
key={theme.id}
theme={theme}
selected={selectedThemeId === theme.id}
locked={false}
onSelect={() => onSelect(theme.id)}
/>
{/* Scaffold preview — browser chrome frame */}
<div className="flex-1 rounded-xl overflow-hidden border" style={{ minHeight: 460 }}>
{/* Browser chrome */}
<div className="flex items-center gap-1.5 px-3 h-8 border-b bg-muted/50 shrink-0">
<div className="w-2.5 h-2.5 rounded-full bg-zinc-300" />
<div className="w-2.5 h-2.5 rounded-full bg-zinc-300" />
<div className="w-2.5 h-2.5 rounded-full bg-zinc-300" />
<div className="ml-3 flex-1 max-w-xs h-5 rounded-md bg-border/60 flex items-center px-2">
<span className="text-[9px] text-muted-foreground truncate">
{activeTheme ? `/${surface.id}${activeTheme.name}` : ""}
</span>
</div>
</div>
{/* Scaffold */}
<div style={{ height: 460 }}>
{ScaffoldComponent
? <ScaffoldComponent />
: (
<div className="h-full flex items-center justify-center text-muted-foreground text-xs">
Select a library above to preview
</div>
)
}
</div>
</div>
{/* Theme description */}
{activeTheme && (
<div className="pt-3 flex items-center gap-3">
<p className="text-xs text-muted-foreground flex-1">{activeTheme.description}</p>
<div className="flex gap-1">
{activeTheme.tags.map(t => (
<Badge key={t} variant="secondary" className="text-[9px] px-1.5 py-0">{t}</Badge>
))}
</div>
)}
{/* Locked state — show only selected */}
{lockedThemeId && (
<div className="flex gap-3 overflow-x-auto pb-2 -mx-1 px-1">
{surface.themes.map(theme => (
<ThemeCard
key={theme.id}
theme={theme}
selected={theme.id === lockedThemeId}
locked={true}
onSelect={() => {}}
/>
))}
</div>
)}
</div>

View File

@@ -0,0 +1,653 @@
"use client";
/**
* Design scaffolds — styled React mockups showing what each UI library
* looks like for a given surface. Used in the Design page preview area.
*
* Each scaffold is rendered at its natural size inside a scaled container,
* giving the user a realistic feel for the library's visual language.
*/
// ---------------------------------------------------------------------------
// Shared mock data
// ---------------------------------------------------------------------------
const NAV_ITEMS = ["Dashboard", "Projects", "Analytics", "Settings"];
const TABLE_ROWS = [
{ name: "Alice Martin", email: "alice@co.com", status: "Active", date: "Jan 12" },
{ name: "Ben Walsh", email: "ben@co.com", status: "Pending", date: "Jan 14" },
{ name: "Clara Kim", email: "clara@co.com", status: "Active", date: "Jan 15" },
{ name: "David Osei", email: "david@co.com", status: "Inactive", date: "Jan 16" },
];
// ---------------------------------------------------------------------------
// WEB APP scaffolds
// ---------------------------------------------------------------------------
export function WebAppShadcn() {
return (
<div className="flex h-full bg-white font-sans text-sm">
{/* Sidebar */}
<div className="w-48 border-r flex flex-col py-4 px-3 gap-1 bg-white">
<div className="flex items-center gap-2 px-2 mb-4">
<div className="w-6 h-6 rounded bg-zinc-900" />
<span className="font-semibold text-zinc-900 text-xs">Acme Inc</span>
</div>
{NAV_ITEMS.map((item, i) => (
<div key={item} className={`flex items-center gap-2 px-2 py-1.5 rounded-md text-xs cursor-pointer ${i === 0 ? "bg-zinc-100 text-zinc-900 font-medium" : "text-zinc-500 hover:bg-zinc-50"}`}>
<div className="w-3.5 h-3.5 rounded-sm bg-zinc-300" />
{item}
</div>
))}
</div>
{/* Main */}
<div className="flex-1 flex flex-col">
<div className="h-12 border-b flex items-center justify-between px-5">
<span className="font-semibold text-zinc-900 text-sm">Dashboard</span>
<div className="flex items-center gap-2">
<div className="h-7 px-3 rounded-md border border-zinc-200 text-[11px] flex items-center text-zinc-600">Export</div>
<div className="h-7 px-3 rounded-md bg-zinc-900 text-[11px] flex items-center text-white">New project</div>
</div>
</div>
<div className="flex-1 p-5 bg-zinc-50">
<div className="grid grid-cols-3 gap-3 mb-4">
{["Total Revenue", "Active Users", "Conversions"].map((label, i) => (
<div key={label} className="bg-white rounded-lg border p-3">
<p className="text-[10px] text-zinc-500 mb-1">{label}</p>
<p className="text-lg font-bold text-zinc-900">{["$12,400", "2,841", "18.2%"][i]}</p>
<p className="text-[10px] text-zinc-400">+{[12, 8, 3][i]}% from last month</p>
</div>
))}
</div>
<div className="bg-white rounded-lg border">
<div className="px-4 py-3 border-b flex items-center justify-between">
<span className="text-xs font-semibold text-zinc-700">Recent activity</span>
<div className="h-6 px-2 rounded border border-zinc-200 text-[10px] flex items-center text-zinc-500">Filter</div>
</div>
<table className="w-full text-[11px]">
<thead><tr className="border-b bg-zinc-50">{["Name","Email","Status","Date"].map(h=><th key={h} className="px-4 py-2 text-left text-[10px] text-zinc-500 font-medium">{h}</th>)}</tr></thead>
<tbody>{TABLE_ROWS.map(r=><tr key={r.name} className="border-b last:border-0 hover:bg-zinc-50"><td className="px-4 py-2 font-medium text-zinc-800">{r.name}</td><td className="px-4 py-2 text-zinc-500">{r.email}</td><td className="px-4 py-2"><span className={`px-1.5 py-0.5 rounded text-[9px] font-medium ${r.status==="Active"?"bg-zinc-100 text-zinc-700":r.status==="Pending"?"bg-zinc-100 text-zinc-500":"bg-zinc-50 text-zinc-400"}`}>{r.status}</span></td><td className="px-4 py-2 text-zinc-400">{r.date}</td></tr>)}</tbody>
</table>
</div>
</div>
</div>
</div>
);
}
export function WebAppMantine() {
return (
<div className="flex h-full font-sans text-sm" style={{ background: "#f8f9fa" }}>
<div className="w-48 flex flex-col py-4 px-3 gap-0.5" style={{ background: "#fff", borderRight: "1px solid #e9ecef" }}>
<div className="flex items-center gap-2 px-2 mb-4">
<div className="w-6 h-6 rounded" style={{ background: "#228be6" }} />
<span className="font-bold text-xs" style={{ color: "#1c7ed6" }}>Acme Inc</span>
</div>
{NAV_ITEMS.map((item, i) => (
<div key={item} className="flex items-center gap-2 px-2 py-2 rounded text-xs cursor-pointer" style={i===0?{background:"#e7f5ff",color:"#1971c2",fontWeight:600}:{color:"#495057"}}>
<div className="w-3.5 h-3.5 rounded-sm" style={{ background: i===0?"#74c0fc":"#ced4da" }} />
{item}
</div>
))}
</div>
<div className="flex-1 flex flex-col">
<div className="h-12 flex items-center justify-between px-5" style={{ background:"#fff", borderBottom:"1px solid #e9ecef" }}>
<span className="font-bold text-sm" style={{ color: "#212529" }}>Dashboard</span>
<div className="flex gap-2">
<button className="h-7 px-3 rounded text-[11px] font-medium" style={{ border:"1px solid #dee2e6", color:"#495057" }}>Export</button>
<button className="h-7 px-3 rounded text-[11px] font-medium text-white" style={{ background:"#228be6" }}>+ New project</button>
</div>
</div>
<div className="flex-1 p-5">
<div className="grid grid-cols-3 gap-3 mb-4">
{["Total Revenue","Active Users","Conversions"].map((label,i)=>(
<div key={label} className="rounded-lg p-3" style={{ background:"#fff", border:"1px solid #e9ecef" }}>
<p className="text-[10px] mb-1" style={{ color:"#868e96" }}>{label}</p>
<p className="text-lg font-bold" style={{ color:"#212529" }}>{["$12,400","2,841","18.2%"][i]}</p>
<p className="text-[10px]" style={{ color:"#228be6" }}> {[12,8,3][i]}% this month</p>
</div>
))}
</div>
<div className="rounded-lg" style={{ background:"#fff", border:"1px solid #e9ecef" }}>
<div className="px-4 py-3 flex items-center justify-between" style={{ borderBottom:"1px solid #e9ecef" }}>
<span className="text-xs font-semibold" style={{ color:"#212529" }}>Users</span>
<input className="h-6 px-2 text-[10px] rounded" style={{ border:"1px solid #dee2e6", outline:"none", width:120 }} placeholder="Search..." />
</div>
<table className="w-full text-[11px]">
<thead><tr style={{ borderBottom:"1px solid #e9ecef", background:"#f8f9fa" }}>{["Name","Email","Status","Date"].map(h=><th key={h} className="px-4 py-2 text-left text-[10px] font-semibold" style={{ color:"#868e96" }}>{h}</th>)}</tr></thead>
<tbody>{TABLE_ROWS.map(r=><tr key={r.name} style={{ borderBottom:"1px solid #f1f3f5" }}><td className="px-4 py-2 font-semibold" style={{ color:"#212529" }}>{r.name}</td><td className="px-4 py-2" style={{ color:"#868e96" }}>{r.email}</td><td className="px-4 py-2"><span className="px-1.5 py-0.5 rounded text-[9px] font-medium" style={r.status==="Active"?{background:"#d3f9d8",color:"#2f9e44"}:r.status==="Pending"?{background:"#fff3bf",color:"#e67700"}:{background:"#f1f3f5",color:"#868e96"}}>{r.status}</span></td><td className="px-4 py-2" style={{ color:"#adb5bd" }}>{r.date}</td></tr>)}</tbody>
</table>
</div>
</div>
</div>
</div>
);
}
export function WebAppHeroUI() {
return (
<div className="flex h-full font-sans text-sm" style={{ background: "#fafafa" }}>
<div className="w-48 flex flex-col py-4 px-3 gap-1" style={{ background: "linear-gradient(180deg,#1a1a2e,#16213e)", color: "#fff" }}>
<div className="flex items-center gap-2 px-2 mb-4">
<div className="w-6 h-6 rounded-full" style={{ background: "linear-gradient(135deg,#7c3aed,#ec4899)" }} />
<span className="font-bold text-xs text-white">Acme Inc</span>
</div>
{NAV_ITEMS.map((item, i) => (
<div key={item} className="flex items-center gap-2 px-2 py-2 rounded-xl text-xs cursor-pointer" style={i===0?{background:"rgba(124,58,237,0.3)",color:"#c084fc",fontWeight:600}:{color:"rgba(255,255,255,0.5)"}}>
<div className="w-3.5 h-3.5 rounded-full" style={{ background: i===0?"#a855f7":"rgba(255,255,255,0.2)" }} />
{item}
</div>
))}
</div>
<div className="flex-1 flex flex-col">
<div className="h-12 flex items-center justify-between px-5 bg-white" style={{ borderBottom: "1px solid #f0f0f0" }}>
<span className="font-bold text-sm" style={{ background:"linear-gradient(90deg,#7c3aed,#ec4899)", WebkitBackgroundClip:"text", WebkitTextFillColor:"transparent" }}>Dashboard</span>
<div className="flex gap-2">
<button className="h-7 px-3 rounded-full text-[11px] font-medium" style={{ border:"1px solid #e4e4e7", color:"#71717a" }}>Export</button>
<button className="h-7 px-3 rounded-full text-[11px] font-semibold text-white" style={{ background:"linear-gradient(135deg,#7c3aed,#ec4899)" }}>+ New project</button>
</div>
</div>
<div className="flex-1 p-5">
<div className="grid grid-cols-3 gap-3 mb-4">
{["Total Revenue","Active Users","Conversions"].map((label,i)=>(
<div key={label} className="rounded-2xl p-3" style={{ background:"#fff", boxShadow:"0 2px 8px rgba(0,0,0,0.06)", border:"1px solid #f4f4f5" }}>
<p className="text-[10px] mb-1" style={{ color:"#a1a1aa" }}>{label}</p>
<p className="text-lg font-bold" style={{ color:"#18181b" }}>{["$12,400","2,841","18.2%"][i]}</p>
<p className="text-[10px]" style={{ color:"#a855f7" }}>+{[12,8,3][i]}% from last month</p>
</div>
))}
</div>
<div className="rounded-2xl" style={{ background:"#fff", boxShadow:"0 2px 8px rgba(0,0,0,0.06)", border:"1px solid #f4f4f5" }}>
<div className="px-4 py-3 flex items-center justify-between" style={{ borderBottom:"1px solid #f4f4f5" }}>
<span className="text-xs font-semibold" style={{ color:"#18181b" }}>Recent activity</span>
<button className="h-6 px-2.5 rounded-full text-[10px] font-medium" style={{ background:"rgba(124,58,237,0.1)", color:"#7c3aed" }}>View all</button>
</div>
<table className="w-full text-[11px]">
<thead><tr style={{ borderBottom:"1px solid #f4f4f5" }}>{["Name","Email","Status","Date"].map(h=><th key={h} className="px-4 py-2 text-left text-[10px] font-medium" style={{ color:"#a1a1aa" }}>{h}</th>)}</tr></thead>
<tbody>{TABLE_ROWS.map(r=><tr key={r.name} style={{ borderBottom:"1px solid #fafafa" }}><td className="px-4 py-2 font-semibold" style={{ color:"#18181b" }}>{r.name}</td><td className="px-4 py-2" style={{ color:"#a1a1aa" }}>{r.email}</td><td className="px-4 py-2"><span className="px-2 py-0.5 rounded-full text-[9px] font-semibold" style={r.status==="Active"?{background:"rgba(34,197,94,0.1)",color:"#16a34a"}:r.status==="Pending"?{background:"rgba(234,179,8,0.1)",color:"#a16207"}:{background:"#f4f4f5",color:"#a1a1aa"}}>{r.status}</span></td><td className="px-4 py-2" style={{ color:"#d4d4d8" }}>{r.date}</td></tr>)}</tbody>
</table>
</div>
</div>
</div>
</div>
);
}
export function WebAppTremor() {
return (
<div className="flex h-full font-sans text-sm bg-white">
<div className="w-48 border-r flex flex-col py-4 px-3 gap-1" style={{ background:"#fff" }}>
<div className="flex items-center gap-2 px-2 mb-4">
<div className="w-6 h-6 rounded" style={{ background:"#3b82f6" }} />
<span className="font-bold text-xs" style={{ color:"#1e3a5f" }}>Acme Inc</span>
</div>
{NAV_ITEMS.map((item, i) => (
<div key={item} className="flex items-center gap-2 px-2 py-2 rounded text-xs cursor-pointer" style={i===0?{background:"#eff6ff",color:"#1d4ed8",fontWeight:600}:{color:"#6b7280"}}>
<div className="w-3.5 h-3.5 rounded" style={{ background:i===0?"#93c5fd":"#e5e7eb" }} />
{item}
</div>
))}
</div>
<div className="flex-1 flex flex-col" style={{ background:"#f9fafb" }}>
<div className="h-12 flex items-center px-5 bg-white" style={{ borderBottom:"1px solid #e5e7eb" }}>
<span className="font-bold text-sm" style={{ color:"#111827" }}>Overview</span>
</div>
<div className="flex-1 p-5">
<div className="grid grid-cols-3 gap-3 mb-4">
{[{l:"Revenue",v:"$12,400",c:"#2563eb",bg:"#dbeafe"},{l:"Users",v:"2,841",c:"#7c3aed",bg:"#ede9fe"},{l:"Conversion",v:"18.2%",c:"#059669",bg:"#d1fae5"}].map(item=>(
<div key={item.l} className="rounded-xl p-3 bg-white" style={{ border:"1px solid #e5e7eb" }}>
<div className="flex items-center justify-between mb-2">
<p className="text-[10px] font-medium" style={{ color:"#6b7280" }}>{item.l}</p>
<div className="w-6 h-6 rounded" style={{ background:item.bg }}><div className="w-full h-full rounded flex items-center justify-center"><div className="w-2.5 h-2.5 rounded-sm" style={{ background:item.c }} /></div></div>
</div>
<p className="text-lg font-bold" style={{ color:"#111827" }}>{item.v}</p>
<div className="mt-2 h-1.5 rounded-full bg-gray-100"><div className="h-full rounded-full" style={{ width:`${[65,48,72][["Revenue","Users","Conversion"].indexOf(item.l)]}%`, background:item.c }} /></div>
</div>
))}
</div>
<div className="bg-white rounded-xl p-4" style={{ border:"1px solid #e5e7eb" }}>
<p className="text-xs font-semibold mb-3" style={{ color:"#111827" }}>Revenue over time</p>
<div className="flex items-end gap-1.5 h-24">
{[40,65,55,80,70,90,75,85,60,95,80,100].map((h,i)=>(
<div key={i} className="flex-1 rounded-t" style={{ height:`${h}%`, background:i===11?"#2563eb":"#dbeafe" }} />
))}
</div>
<div className="flex justify-between mt-1">
{["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"].map(m=><span key={m} className="text-[8px]" style={{ color:"#9ca3af" }}>{m}</span>)}
</div>
</div>
</div>
</div>
</div>
);
}
// ---------------------------------------------------------------------------
// MARKETING scaffolds
// ---------------------------------------------------------------------------
export function MarketingDaisy() {
return (
<div className="h-full font-sans" style={{ background:"#1d232a", color:"#a6adba" }}>
<nav className="flex items-center justify-between px-6 py-3" style={{ borderBottom:"1px solid rgba(255,255,255,0.1)" }}>
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded" style={{ background:"#f59e0b" }} />
<span className="font-bold text-white text-sm">Acme</span>
</div>
<div className="flex gap-4 text-xs" style={{ color:"rgba(255,255,255,0.6)" }}>
{["Features","Pricing","Docs","Blog"].map(i=><span key={i} className="cursor-pointer hover:text-white">{i}</span>)}
</div>
<div className="flex gap-2">
<button className="h-7 px-3 rounded-full text-[11px] font-bold" style={{ background:"rgba(255,255,255,0.1)", color:"white" }}>Login</button>
<button className="h-7 px-3 rounded-full text-[11px] font-bold text-black" style={{ background:"#f59e0b" }}>Get started</button>
</div>
</nav>
<div className="px-10 pt-12 pb-6 text-center">
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-[10px] font-bold mb-4" style={{ background:"rgba(245,158,11,0.15)", color:"#f59e0b", border:"1px solid rgba(245,158,11,0.3)" }}>
New v2.0 is here
</div>
<h1 className="text-2xl font-black text-white mb-2">Build faster,<br />ship smarter</h1>
<p className="text-xs mb-6" style={{ color:"rgba(255,255,255,0.5)", maxWidth:280, margin:"0 auto 20px" }}>The all-in-one platform that helps teams build, launch, and scale their products.</p>
<div className="flex gap-2 justify-center">
<button className="h-9 px-5 rounded-full text-xs font-bold text-black" style={{ background:"#f59e0b" }}>Start for free</button>
<button className="h-9 px-5 rounded-full text-xs font-bold" style={{ border:"1px solid rgba(255,255,255,0.2)", color:"white" }}>See demo </button>
</div>
</div>
<div className="grid grid-cols-3 gap-3 px-8 mt-2">
{[{icon:"⚡",title:"Lightning fast",desc:"Deploy in seconds, not hours"},{icon:"🔒",title:"Secure by default",desc:"Enterprise-grade security built in"},{icon:"📈",title:"Scales with you",desc:"From zero to millions of users"}].map(f=>(
<div key={f.title} className="p-3 rounded-xl text-center" style={{ background:"rgba(255,255,255,0.05)", border:"1px solid rgba(255,255,255,0.08)" }}>
<div className="text-xl mb-1">{f.icon}</div>
<p className="text-[11px] font-bold text-white mb-0.5">{f.title}</p>
<p className="text-[10px]" style={{ color:"rgba(255,255,255,0.4)" }}>{f.desc}</p>
</div>
))}
</div>
</div>
);
}
export function MarketingHeroUI() {
return (
<div className="h-full font-sans bg-white">
<nav className="flex items-center justify-between px-6 py-3 border-b">
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded-full" style={{ background:"linear-gradient(135deg,#7c3aed,#ec4899)" }} />
<span className="font-bold text-xs" style={{ color:"#18181b" }}>Acme</span>
</div>
<div className="flex gap-4 text-xs text-zinc-500">
{["Features","Pricing","Docs","Blog"].map(i=><span key={i}>{i}</span>)}
</div>
<div className="flex gap-2">
<button className="h-7 px-3 rounded-full text-[11px]" style={{ border:"1px solid #e4e4e7", color:"#71717a" }}>Login</button>
<button className="h-7 px-3 rounded-full text-[11px] font-semibold text-white" style={{ background:"linear-gradient(135deg,#7c3aed,#ec4899)" }}>Get started</button>
</div>
</nav>
<div className="px-10 pt-10 pb-6 text-center">
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-[10px] font-semibold mb-4" style={{ background:"rgba(124,58,237,0.08)", color:"#7c3aed", border:"1px solid rgba(124,58,237,0.15)" }}>
🚀 Just launched v2.0
</div>
<h1 className="text-2xl font-black mb-2" style={{ background:"linear-gradient(135deg,#7c3aed,#ec4899,#f97316)", WebkitBackgroundClip:"text", WebkitTextFillColor:"transparent" }}>
Build faster,<br />ship smarter
</h1>
<p className="text-xs text-zinc-500 mb-6 mx-auto" style={{ maxWidth:280 }}>The all-in-one platform for teams that move fast.</p>
<div className="flex gap-2 justify-center mb-8">
<button className="h-9 px-5 rounded-full text-xs font-semibold text-white shadow-lg" style={{ background:"linear-gradient(135deg,#7c3aed,#ec4899)" }}>Start for free</button>
<button className="h-9 px-5 rounded-full text-xs font-medium" style={{ border:"1px solid #e4e4e7", color:"#71717a" }}>Live demo </button>
</div>
<div className="rounded-2xl mx-auto overflow-hidden shadow-2xl" style={{ maxWidth:400, border:"1px solid #f4f4f5" }}>
<div className="h-6 flex items-center gap-1.5 px-3" style={{ background:"#fafafa", borderBottom:"1px solid #f4f4f5" }}>
{["#ff5f56","#ffbd2e","#27c93f"].map(c=><div key={c} className="w-2 h-2 rounded-full" style={{ background:c }} />)}
</div>
<div className="p-4" style={{ background:"#fff" }}>
<div className="grid grid-cols-2 gap-2">
{["Revenue","Users","Growth","Churn"].map((m,i)=><div key={m} className="rounded-xl p-2.5" style={{ background:"linear-gradient(135deg,rgba(124,58,237,0.05),rgba(236,72,153,0.05))", border:"1px solid rgba(124,58,237,0.1)" }}><p className="text-[9px] text-zinc-400">{m}</p><p className="text-sm font-bold" style={{ color:"#18181b" }}>{["$12k","2.8k","+24%","2.1%"][i]}</p></div>)}
</div>
</div>
</div>
</div>
</div>
);
}
export function MarketingAceternity() {
return (
<div className="h-full font-sans" style={{ background:"#030303" }}>
<nav className="flex items-center justify-between px-6 py-3" style={{ borderBottom:"1px solid rgba(255,255,255,0.06)" }}>
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded" style={{ background:"linear-gradient(135deg,#a855f7,#3b82f6)" }} />
<span className="font-bold text-white text-sm">Acme</span>
</div>
<div className="flex gap-4 text-xs" style={{ color:"rgba(255,255,255,0.4)" }}>
{["Features","Pricing","Docs"].map(i=><span key={i}>{i}</span>)}
</div>
<button className="h-7 px-3 rounded text-[11px] font-medium text-white" style={{ background:"rgba(168,85,247,0.2)", border:"1px solid rgba(168,85,247,0.3)" }}>Get started</button>
</nav>
<div className="relative px-10 pt-10 text-center overflow-hidden">
<div className="absolute inset-0 pointer-events-none" style={{ background:"radial-gradient(ellipse 60% 40% at 50% 0%,rgba(168,85,247,0.15),transparent)" }} />
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-[10px] font-medium mb-4" style={{ border:"1px solid rgba(168,85,247,0.3)", color:"rgba(168,85,247,0.9)" }}>
Open source · 12k GitHub stars
</div>
<h1 className="text-2xl font-black mb-2" style={{ background:"linear-gradient(180deg,#fff 0%,rgba(255,255,255,0.5) 100%)", WebkitBackgroundClip:"text", WebkitTextFillColor:"transparent" }}>
Build the future<br />of the web
</h1>
<p className="text-xs mb-6 mx-auto" style={{ color:"rgba(255,255,255,0.35)", maxWidth:260 }}>Beautifully crafted components built with Tailwind CSS and Framer Motion.</p>
<div className="flex gap-2 justify-center mb-6">
<button className="h-9 px-5 rounded text-xs font-semibold text-white relative overflow-hidden" style={{ background:"linear-gradient(135deg,#a855f7,#3b82f6)" }}>Get started </button>
<button className="h-9 px-5 rounded text-xs font-medium" style={{ border:"1px solid rgba(255,255,255,0.1)", color:"rgba(255,255,255,0.6)" }}>View components</button>
</div>
<div className="grid grid-cols-3 gap-3 mx-auto" style={{ maxWidth:480 }}>
{["Animated","Accessible","Open source"].map((f,i)=>(
<div key={f} className="p-3 rounded-xl text-left" style={{ background:"rgba(255,255,255,0.03)", border:"1px solid rgba(255,255,255,0.06)" }}>
<div className="w-6 h-6 rounded mb-1.5 flex items-center justify-center" style={{ background:["rgba(168,85,247,0.15)","rgba(59,130,246,0.15)","rgba(34,197,94,0.15)"][i] }}>
<div className="w-2.5 h-2.5 rounded-full" style={{ background:["#a855f7","#3b82f6","#22c55e"][i] }} />
</div>
<p className="text-[10px] font-semibold text-white mb-0.5">{f}</p>
<p className="text-[9px]" style={{ color:"rgba(255,255,255,0.3)" }}>Built for production</p>
</div>
))}
</div>
</div>
</div>
);
}
export function MarketingTailwind() {
return (
<div className="h-full font-sans bg-white">
<nav className="flex items-center justify-between px-6 py-3 border-b">
<span className="font-black text-sm text-zinc-900">acme</span>
<div className="flex gap-4 text-xs text-zinc-500">
{["Features","Pricing","Blog"].map(i=><span key={i}>{i}</span>)}
</div>
<div className="flex gap-2">
<button className="h-7 px-3 text-[11px] text-zinc-600">Log in</button>
<button className="h-7 px-3 rounded-md text-[11px] font-medium bg-zinc-900 text-white">Sign up</button>
</div>
</nav>
<div className="px-10 pt-10 pb-6">
<div className="max-w-sm">
<span className="text-[10px] font-semibold text-violet-600 tracking-widest uppercase">Now in public beta</span>
<h1 className="text-2xl font-black text-zinc-900 mt-1 mb-2 leading-tight">The platform<br />built for scale</h1>
<p className="text-xs text-zinc-500 mb-5 leading-relaxed">Everything your team needs to build, deploy, and monitor production applications.</p>
<div className="flex gap-2 mb-8">
<button className="h-8 px-4 rounded-md text-xs font-medium bg-zinc-900 text-white">Get started free</button>
<button className="h-8 px-4 rounded-md text-xs font-medium border border-zinc-200 text-zinc-600">Documentation </button>
</div>
<div className="grid grid-cols-2 gap-2">
{["99.9% uptime SLA","10ms avg latency","SOC2 compliant","GDPR ready"].map(f=>(
<div key={f} className="flex items-center gap-1.5 text-[10px] text-zinc-500">
<div className="w-3.5 h-3.5 rounded-full bg-zinc-900 flex items-center justify-center">
<div className="w-1.5 h-1 border-l border-b border-white -rotate-45 translate-y-px" />
</div>
{f}
</div>
))}
</div>
</div>
</div>
</div>
);
}
// ---------------------------------------------------------------------------
// ADMIN scaffolds
// ---------------------------------------------------------------------------
export function AdminMantine() { return <WebAppMantine />; }
export function AdminShadcn() { return <WebAppShadcn />; }
export function AdminTremor() { return <WebAppTremor />; }
// ---------------------------------------------------------------------------
// MOBILE scaffolds
// ---------------------------------------------------------------------------
function MobileFrame({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center justify-center h-full" style={{ background:"#f4f4f5" }}>
<div className="relative" style={{ width:200, height:380 }}>
<div className="absolute inset-0 rounded-3xl" style={{ background:"#1a1a1a", boxShadow:"0 20px 60px rgba(0,0,0,0.3)" }} />
<div className="absolute top-3 left-1/2 -translate-x-1/2 w-16 h-4 rounded-full bg-black z-10" />
<div className="absolute inset-2 rounded-3xl overflow-hidden bg-white">
{children}
</div>
</div>
</div>
);
}
export function MobileNativewind() {
return (
<MobileFrame>
<div className="h-full flex flex-col bg-white text-xs">
<div className="pt-6 pb-3 px-3" style={{ background:"#18181b" }}>
<p className="text-[8px] text-zinc-400">Good morning</p>
<p className="text-sm font-bold text-white">Dashboard</p>
</div>
<div className="flex-1 overflow-auto p-3 space-y-2" style={{ background:"#fafafa" }}>
<div className="grid grid-cols-2 gap-2">
{[{l:"Revenue",v:"$4.2k"},{l:"Users",v:"184"}].map(c=>(
<div key={c.l} className="bg-white rounded-xl p-2.5" style={{ border:"1px solid #e4e4e7" }}>
<p className="text-[8px] text-zinc-400">{c.l}</p>
<p className="text-sm font-bold text-zinc-900">{c.v}</p>
</div>
))}
</div>
{["Fix login bug","Update pricing","Review PR #42"].map((t,i)=>(
<div key={t} className="bg-white rounded-xl p-2.5 flex items-center gap-2" style={{ border:"1px solid #e4e4e7" }}>
<div className="w-4 h-4 rounded border border-zinc-300 shrink-0" style={i===0?{background:"#18181b",border:"none"}:{}} />
<p className="text-[9px] text-zinc-700" style={i===0?{textDecoration:"line-through",color:"#a1a1aa"}:{}}>{t}</p>
</div>
))}
</div>
<div className="flex border-t bg-white" style={{ padding:"6px 0 10px" }}>
{["Home","Projects","Chat","Profile"].map((l,i)=>(
<div key={l} className="flex-1 flex flex-col items-center gap-0.5">
<div className="w-4 h-4 rounded" style={{ background:i===0?"#18181b":"#e4e4e7" }} />
<span className="text-[7px]" style={{ color:i===0?"#18181b":"#a1a1aa" }}>{l}</span>
</div>
))}
</div>
</div>
</MobileFrame>
);
}
export function MobileGluestack() {
return (
<MobileFrame>
<div className="h-full flex flex-col text-xs">
<div className="pt-6 pb-3 px-3 flex items-center justify-between" style={{ background:"#1976d2" }}>
<p className="text-sm font-bold text-white">Dashboard</p>
<div className="w-5 h-5 rounded-full bg-white/20" />
</div>
<div className="flex-1 overflow-auto p-3 space-y-2" style={{ background:"#f5f5f5" }}>
<div className="grid grid-cols-2 gap-2">
{[{l:"Revenue",v:"$4.2k",c:"#1976d2"},{l:"Users",v:"184",c:"#7b1fa2"}].map(c=>(
<div key={c.l} className="rounded-lg p-2.5" style={{ background:c.c }}>
<p className="text-[8px] text-white/70">{c.l}</p>
<p className="text-sm font-bold text-white">{c.v}</p>
</div>
))}
</div>
{["Fix login bug","Update pricing","Review PR #42"].map((t,i)=>(
<div key={t} className="bg-white rounded-lg p-2.5 flex items-center gap-2 shadow-sm">
<div className="w-4 h-4 rounded-full border-2 shrink-0" style={i===0?{background:"#1976d2",borderColor:"#1976d2"}:{borderColor:"#bdbdbd"}} />
<p className="text-[9px]" style={{ color:i===0?"#9e9e9e":"#212121", textDecoration:i===0?"line-through":"none" }}>{t}</p>
</div>
))}
</div>
<div className="flex bg-white shadow-sm" style={{ padding:"6px 0 10px", borderTop:"1px solid #e0e0e0" }}>
{["Home","Tasks","Chat","Profile"].map((l,i)=>(
<div key={l} className="flex-1 flex flex-col items-center gap-0.5">
<div className="w-4 h-4 rounded" style={{ background:i===0?"#1976d2":"#e0e0e0" }} />
<span className="text-[7px]" style={{ color:i===0?"#1976d2":"#9e9e9e" }}>{l}</span>
</div>
))}
</div>
</div>
</MobileFrame>
);
}
// ---------------------------------------------------------------------------
// EMAIL scaffolds
// ---------------------------------------------------------------------------
export function EmailReactEmail() {
return (
<div className="flex items-start justify-center h-full py-6 font-sans" style={{ background:"#f6f9fc" }}>
<div className="w-full max-w-md" style={{ background:"#fff", borderRadius:8, boxShadow:"0 2px 8px rgba(0,0,0,0.08)" }}>
<div className="px-8 py-5" style={{ borderBottom:"1px solid #e6e6e6" }}>
<div className="flex items-center gap-2 mb-1">
<div className="w-5 h-5 rounded" style={{ background:"#000" }} />
<span className="font-bold text-xs text-zinc-900">Acme</span>
</div>
</div>
<div className="px-8 py-6">
<p className="text-base font-bold text-zinc-900 mb-2">Welcome to Acme! 🎉</p>
<p className="text-xs text-zinc-500 mb-4 leading-relaxed">Hi Alice, thanks for signing up. Your account is ready and you can start building right away.</p>
<div className="text-center mb-4">
<button className="h-9 px-6 rounded text-xs font-semibold text-white" style={{ background:"#000" }}>Get started </button>
</div>
<div className="rounded-lg p-3 mb-4" style={{ background:"#f6f9fc", border:"1px solid #e6e6e6" }}>
<p className="text-[10px] font-semibold text-zinc-700 mb-1">Your account details</p>
{["Plan: Starter","Workspace: alice-workspace","Status: Active"].map(d=>(
<p key={d} className="text-[10px] text-zinc-500">{d}</p>
))}
</div>
<p className="text-[10px] text-zinc-400 leading-relaxed">If you have any questions, reply to this email or visit our help center. We're here to help.</p>
</div>
<div className="px-8 py-4 text-center" style={{ borderTop:"1px solid #e6e6e6", background:"#fafafa" }}>
<p className="text-[9px] text-zinc-400">Acme Inc · 123 Main St · San Francisco CA · Unsubscribe</p>
</div>
</div>
</div>
);
}
// ---------------------------------------------------------------------------
// DOCS scaffolds
// ---------------------------------------------------------------------------
export function DocsNextra() {
return (
<div className="flex h-full font-sans text-xs bg-white">
<div className="w-44 border-r flex flex-col py-4 px-3" style={{ background:"#fafafa" }}>
<div className="mb-4">
<p className="text-[9px] font-semibold text-zinc-400 uppercase tracking-wide mb-2">Getting Started</p>
{["Introduction","Installation","Quick Start"].map((item,i)=>(
<div key={item} className={`px-2 py-1 rounded text-[10px] cursor-pointer mb-0.5 ${i===0?"bg-zinc-900 text-white font-medium":"text-zinc-500 hover:text-zinc-700"}`}>{item}</div>
))}
</div>
<div className="mb-4">
<p className="text-[9px] font-semibold text-zinc-400 uppercase tracking-wide mb-2">Components</p>
{["Button","Card","Input","Modal","Table"].map(item=>(
<div key={item} className="px-2 py-1 rounded text-[10px] cursor-pointer text-zinc-500 hover:text-zinc-700 mb-0.5">{item}</div>
))}
</div>
<div>
<p className="text-[9px] font-semibold text-zinc-400 uppercase tracking-wide mb-2">API Reference</p>
{["REST API","Webhooks"].map(item=>(
<div key={item} className="px-2 py-1 text-[10px] text-zinc-500 mb-0.5">{item}</div>
))}
</div>
</div>
<div className="flex-1 overflow-auto">
<div className="max-w-lg px-8 py-6">
<div className="flex items-center gap-1.5 text-[9px] text-zinc-400 mb-4">
<span>Docs</span><span>/</span><span>Getting Started</span><span>/</span><span className="text-zinc-600">Introduction</span>
</div>
<h1 className="text-base font-bold text-zinc-900 mb-2">Introduction</h1>
<p className="text-[11px] text-zinc-600 mb-3 leading-relaxed">Acme UI is a collection of re-usable components that you can copy and paste into your web apps. Built with Radix UI and Tailwind CSS.</p>
<div className="rounded-lg p-3 mb-3" style={{ background:"#18181b" }}>
<p className="text-[10px] font-mono" style={{ color:"#10b981" }}>$ npm install acme-ui</p>
</div>
<h2 className="text-sm font-semibold text-zinc-900 mb-1.5">Key features</h2>
{["Accessible","Customisable","Open source","TypeScript ready"].map(f=>(
<div key={f} className="flex items-center gap-1.5 text-[10px] text-zinc-600 mb-1">
<div className="w-1.5 h-1.5 rounded-full bg-zinc-400" />{f}
</div>
))}
</div>
</div>
</div>
);
}
export function DocsShadcnCustom() {
return (
<div className="flex h-full font-sans text-xs bg-white">
<div className="w-44 border-r flex flex-col py-4 px-3">
<div className="mb-4 pb-3 border-b">
<div className="flex items-center gap-2">
<div className="w-5 h-5 rounded bg-zinc-900" />
<span className="font-bold text-zinc-900 text-[11px]">Acme Docs</span>
</div>
</div>
{[{section:"Guides",items:["Introduction","Installation","Theming"]},{section:"Components",items:["Button","Input","Select","Dialog"]}].map(g=>(
<div key={g.section} className="mb-3">
<p className="text-[9px] font-semibold text-zinc-400 uppercase tracking-wide mb-1.5">{g.section}</p>
{g.items.map((item,i)=>(
<div key={item} className={`px-2 py-1 rounded-md text-[10px] mb-0.5 cursor-pointer ${i===0&&g.section==="Guides"?"bg-zinc-100 text-zinc-900 font-medium":"text-zinc-500"}`}>{item}</div>
))}
</div>
))}
</div>
<div className="flex-1 px-8 py-6 overflow-auto">
<h1 className="text-base font-bold text-zinc-900 mb-1">Introduction</h1>
<p className="text-[11px] text-zinc-500 mb-4 leading-relaxed">A set of beautifully designed components built with Radix UI and Tailwind CSS.</p>
<div className="rounded-lg border overflow-hidden mb-4">
<div className="flex items-center gap-2 px-3 py-2 border-b bg-zinc-50">
<span className="text-[9px] font-medium text-zinc-500">bash</span>
</div>
<div className="px-3 py-2.5 bg-zinc-950">
<p className="text-[10px] font-mono text-emerald-400">npx shadcn@latest init</p>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
{["Button","Card","Input","Badge"].map(c=>(
<div key={c} className="rounded-lg border p-3 cursor-pointer hover:bg-zinc-50 transition-colors">
<p className="text-[10px] font-semibold text-zinc-700 mb-0.5">{c}</p>
<p className="text-[9px] text-zinc-400">View component </p>
</div>
))}
</div>
</div>
</div>
);
}
// ---------------------------------------------------------------------------
// Registry — maps surface+theme to scaffold component
// ---------------------------------------------------------------------------
export const SCAFFOLD_REGISTRY: Record<string, Record<string, React.ComponentType>> = {
"web-app": {
"shadcn": WebAppShadcn,
"mantine": WebAppMantine,
"hero-ui": WebAppHeroUI,
"tremor": WebAppTremor,
},
"marketing": {
"daisy-ui": MarketingDaisy,
"hero-ui": MarketingHeroUI,
"aceternity": MarketingAceternity,
"tailwind-only": MarketingTailwind,
},
"admin": {
"mantine": AdminMantine,
"shadcn": AdminShadcn,
"tremor": AdminTremor,
},
"mobile": {
"nativewind": MobileNativewind,
"gluestack": MobileGluestack,
},
"email": {
"react-email": EmailReactEmail,
},
"docs": {
"nextra": DocsNextra,
"shadcn": DocsShadcnCustom,
},
};