diff --git a/consumer-guide/.gitignore b/consumer-guide/.gitignore
new file mode 100644
index 000000000..9b04f643a
--- /dev/null
+++ b/consumer-guide/.gitignore
@@ -0,0 +1,17 @@
+# v0 sandbox internal files
+__v0_runtime_loader.js
+__v0_devtools.tsx
+__v0_jsx-dev-runtime.ts
+.snowflake/
+.v0-trash/
+.vercel/
+next.user-config.*
+
+# Environment variables
+.env*.local
+
+# Common ignores
+node_modules
+.next/
+.DS_Store
+.vercel
diff --git a/consumer-guide/README.md b/consumer-guide/README.md
new file mode 100644
index 000000000..f11272311
--- /dev/null
+++ b/consumer-guide/README.md
@@ -0,0 +1,29 @@
+# FastVideo Consumer Guide
+
+A standalone Next.js consumer guide for FastVideo configuration selection and advanced tuning.
+
+## Requirements
+
+- Node.js 20 or newer
+- pnpm
+
+## Run Locally
+
+```bash
+pnpm install
+pnpm dev
+```
+
+Then open http://localhost:3000.
+
+## Build
+
+```bash
+pnpm build
+pnpm start
+```
+
+## Notes
+
+This app is independent from the main FastVideo Python package.
+Run commands from this `consumer-guide/` directory.
diff --git a/consumer-guide/app/globals.css b/consumer-guide/app/globals.css
new file mode 100644
index 000000000..dc2aea17f
--- /dev/null
+++ b/consumer-guide/app/globals.css
@@ -0,0 +1,125 @@
+@import 'tailwindcss';
+@import 'tw-animate-css';
+
+@custom-variant dark (&:is(.dark *));
+
+:root {
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --destructive-foreground: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --radius: 0.625rem;
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.145 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.145 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.985 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.396 0.141 25.723);
+ --destructive-foreground: oklch(0.637 0.237 25.331);
+ --border: oklch(0.269 0 0);
+ --input: oklch(0.269 0 0);
+ --ring: oklch(0.439 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(0.269 0 0);
+ --sidebar-ring: oklch(0.439 0 0);
+}
+
+@theme inline {
+ --font-sans: 'Geist', 'Geist Fallback';
+ --font-mono: 'Geist Mono', 'Geist Mono Fallback';
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/consumer-guide/app/layout.tsx b/consumer-guide/app/layout.tsx
new file mode 100644
index 000000000..91eb9484e
--- /dev/null
+++ b/consumer-guide/app/layout.tsx
@@ -0,0 +1,45 @@
+import type { Metadata } from 'next'
+import { Geist, Geist_Mono } from 'next/font/google'
+import { Analytics } from '@vercel/analytics/next'
+import './globals.css'
+
+const _geist = Geist({ subsets: ["latin"] });
+const _geistMono = Geist_Mono({ subsets: ["latin"] });
+
+export const metadata: Metadata = {
+ title: 'FastVideo Config Selector',
+ description: 'Choose your hardware and task to get a recommended FastVideo configuration',
+ generator: 'v0.app',
+ icons: {
+ icon: [
+ {
+ url: '/icon-light-32x32.png',
+ media: '(prefers-color-scheme: light)',
+ },
+ {
+ url: '/icon-dark-32x32.png',
+ media: '(prefers-color-scheme: dark)',
+ },
+ {
+ url: '/icon.svg',
+ type: 'image/svg+xml',
+ },
+ ],
+ apple: '/apple-icon.png',
+ },
+}
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode
+}>) {
+ return (
+
+
+ {children}
+ {process.env.NODE_ENV === 'production' && }
+
+
+ )
+}
diff --git a/consumer-guide/app/page.tsx b/consumer-guide/app/page.tsx
new file mode 100644
index 000000000..9c147e1ff
--- /dev/null
+++ b/consumer-guide/app/page.tsx
@@ -0,0 +1,334 @@
+"use client"
+
+import { useState, useMemo } from "react"
+import { Monitor, Cpu, HardDrive, Layers, Clock, Copy, Check, Film, Image, Sparkles, Gamepad2, Settings2 } from "lucide-react"
+import Link from "next/link"
+
+type Task = "t2v" | "i2v" | "ti2v" | "action"
+type Tier = "quick" | "daily" | "best"
+
+interface Config {
+ model: string
+ modelShort: string
+ attn: string
+ h: number
+ w: number
+ frames: number
+ steps: number
+ cfg: number
+ vramMin: number
+ ramMin: number
+ time: string
+ vram: string
+ offload: boolean
+ extra?: string
+}
+
+const CONFIGS: Record> = {
+ t2v: {
+ quick: { model: "FastVideo/FastWan2.1-T2V-1.3B-Diffusers", modelShort: "FastWan2.1-T2V-1.3B", attn: "VIDEO_SPARSE_ATTN", h: 480, w: 832, frames: 49, steps: 20, cfg: 5.0, vramMin: 8, ramMin: 16, time: "~60 s", vram: "~16 GB", offload: false },
+ daily: { model: "FastVideo/FastWan2.1-T2V-1.3B-Diffusers", modelShort: "FastWan2.1-T2V-1.3B", attn: "FLASH_ATTN", h: 720, w: 1280, frames: 81, steps: 30, cfg: 5.0, vramMin: 20, ramMin: 32, time: "~3 min", vram: "~22 GB", offload: false },
+ best: { model: "Wan-AI/Wan2.1-T2V-14B-Diffusers", modelShort: "Wan2.1-T2V-14B", attn: "FLASH_ATTN", h: 720, w: 1280, frames: 81, steps: 50, cfg: 6.0, vramMin: 16, ramMin: 40, time: "~15 min", vram: "~18 GB + ~40 GB RAM", offload: true }
+ },
+ i2v: {
+ quick: { model: "Wan-AI/Wan2.1-I2V-14B-480P-Diffusers", modelShort: "Wan2.1-I2V-14B-480P", attn: "FLASH_ATTN", h: 480, w: 832, frames: 49, steps: 20, cfg: 5.0, vramMin: 16, ramMin: 32, time: "~2 min", vram: "~18 GB", offload: true, extra: "--image ./input.png" },
+ daily: { model: "Wan-AI/Wan2.1-I2V-14B-480P-Diffusers", modelShort: "Wan2.1-I2V-14B-480P", attn: "FLASH_ATTN", h: 480, w: 832, frames: 81, steps: 30, cfg: 5.0, vramMin: 16, ramMin: 32, time: "~5 min", vram: "~20 GB", offload: true, extra: "--image ./input.png" },
+ best: { model: "Wan-AI/Wan2.1-I2V-14B-720P-Diffusers", modelShort: "Wan2.1-I2V-14B-720P", attn: "FLASH_ATTN", h: 720, w: 1280, frames: 81, steps: 50, cfg: 6.0, vramMin: 16, ramMin: 40, time: "~20 min", vram: "~20 GB + ~40 GB RAM", offload: true, extra: "--image ./input.png" }
+ },
+ ti2v: {
+ quick: { model: "FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers", modelShort: "FastWan2.2-TI2V-5B", attn: "FLASH_ATTN", h: 480, w: 832, frames: 49, steps: 20, cfg: 5.0, vramMin: 16, ramMin: 32, time: "~2 min", vram: "~18 GB", offload: false, extra: "--image ./input.png" },
+ daily: { model: "FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers", modelShort: "FastWan2.2-TI2V-5B", attn: "FLASH_ATTN", h: 720, w: 1280, frames: 81, steps: 30, cfg: 5.0, vramMin: 20, ramMin: 32, time: "~5 min", vram: "~22 GB", offload: false, extra: "--image ./input.png" },
+ best: { model: "Wan-AI/Wan2.2-TI2V-5B-Diffusers", modelShort: "Wan2.2-TI2V-5B", attn: "SAGE_ATTN", h: 720, w: 1280, frames: 81, steps: 50, cfg: 6.0, vramMin: 20, ramMin: 40, time: "~15 min", vram: "~22 GB + ~40 GB RAM", offload: true, extra: "--image ./input.png" }
+ },
+ action: {
+ quick: { model: "FastVideo/Matrix-Game-2.0-Base-Diffusers", modelShort: "Matrix-Game-2.0-Base", attn: "FLASH_ATTN", h: 480, w: 832, frames: 49, steps: 20, cfg: 5.0, vramMin: 16, ramMin: 32, time: "~2 min", vram: "~18 GB", offload: false, extra: "--image ./input.png --action forward" },
+ daily: { model: "FastVideo/Matrix-Game-2.0-Base-Diffusers", modelShort: "Matrix-Game-2.0-Base", attn: "FLASH_ATTN", h: 720, w: 1280, frames: 81, steps: 30, cfg: 5.0, vramMin: 20, ramMin: 32, time: "~5 min", vram: "~22 GB", offload: false, extra: "--image ./input.png --action forward" },
+ best: { model: "FastVideo/Matrix-Game-2.0-GTA-Diffusers", modelShort: "Matrix-Game-2.0-GTA", attn: "FLASH_ATTN", h: 720, w: 1280, frames: 81, steps: 50, cfg: 6.0, vramMin: 20, ramMin: 40, time: "~15 min", vram: "~20 GB + ~40 GB RAM", offload: true, extra: "--image ./input.png --action forward" }
+ }
+}
+
+const TASKS: { id: Task; label: string; sub: string; icon: typeof Film }[] = [
+ { id: "t2v", label: "Text to Video", sub: "T2V", icon: Film },
+ { id: "i2v", label: "Image to Video", sub: "I2V", icon: Image },
+ { id: "ti2v", label: "Text + Image", sub: "TI2V", icon: Sparkles },
+ { id: "action", label: "Action Control", sub: "Matrix Game", icon: Gamepad2 },
+]
+
+const TIERS: { id: Tier; name: string; desc: string; badge?: string }[] = [
+ { id: "quick", name: "Quick Try", desc: "Fast preview. Lower resolution, fewer steps." },
+ { id: "daily", name: "Daily Use", desc: "Balanced quality and speed.", badge: "Recommended" },
+ { id: "best", name: "Best Quality", desc: "14B model, max fidelity. Requires offloading." },
+]
+
+export default function FastVideoConfigSelector() {
+ const [task, setTask] = useState("t2v")
+ const [tier, setTier] = useState("daily")
+ const [gpu, setGpu] = useState("4090")
+ const [vram, setVram] = useState(24)
+ const [ram, setRam] = useState(64)
+ const [ngpu, setNgpu] = useState(1)
+ const [copied, setCopied] = useState(false)
+
+ const config = CONFIGS[task][tier]
+
+ const warning = useMemo(() => {
+ if (vram < config.vramMin) {
+ return `This mode requires ${config.vramMin} GB VRAM. Your selection is ${vram} GB — consider switching to Quick Try.`
+ }
+ if (ram < config.ramMin) {
+ return `This mode requires ${config.ramMin} GB RAM. Your selection is ${ram} GB.`
+ }
+ return null
+ }, [vram, ram, config])
+
+ const command = useMemo(() => {
+ let cmd = `# 1. install\npip install fastvideo\n\n`
+ cmd += `# 2. download model\nhuggingface-cli download ${config.model} \\\n --local-dir ./models/${config.modelShort}\n\n`
+ cmd += `# 3. run\nfastvideo generate \\\n`
+ cmd += ` --model ./models/${config.modelShort} \\\n`
+ cmd += ` --attention ${config.attn} \\\n`
+ cmd += ` --height ${config.h} --width ${config.w} \\\n`
+ cmd += ` --num_frames ${config.frames} \\\n`
+ cmd += ` --num_inference_steps ${config.steps} \\\n`
+ cmd += ` --guidance_scale ${config.cfg.toFixed(1)}`
+ if (config.offload) {
+ cmd += ` \\\n --dit_cpu_offload \\\n --vae_cpu_offload \\\n --pin_cpu_memory`
+ }
+ if (config.extra) {
+ cmd += ` \\\n ${config.extra}`
+ }
+ cmd += ` \\\n --prompt "your prompt here"`
+ return cmd
+ }, [config])
+
+ const copyCommand = async () => {
+ await navigator.clipboard.writeText(command)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
+
Config Selector
+
Choose your hardware and task to get a recommended configuration
+
+
+
+
+ Advanced Tuning
+
+
+
+
+
+
+ {/* Step 1: Task Selection */}
+
+
+ 1
+
What do you want to make?
+
+
+ {TASKS.map(({ id, label, sub, icon: Icon }) => (
+
setTask(id)}
+ className={`group relative rounded-xl border p-4 text-left transition-all duration-200 ${
+ task === id
+ ? "border-sky-500/50 bg-sky-500/10 shadow-lg shadow-sky-500/10"
+ : "border-white/5 bg-white/[0.02] hover:border-white/10 hover:bg-white/[0.04]"
+ }`}
+ >
+
+
+
+
+ {label}
+
+ {sub}
+
+ ))}
+
+
+
+ {/* Step 2: Machine Specs */}
+
+
+ 2
+
Your machine specs
+
+
+
+
+
+ GPU Model
+
+
setGpu(e.target.value)}
+ className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm text-white outline-none transition-colors focus:border-sky-500/50"
+ >
+ RTX 4090
+ RTX 4080
+ RTX 3090
+ RTX 3080
+ Other
+
+
+
+
+
+ VRAM (GB)
+
+
setVram(Number(e.target.value))}
+ className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm text-white outline-none transition-colors focus:border-sky-500/50"
+ >
+ 24 GB
+ 16 GB
+ 12 GB
+ 10 GB
+ 8 GB
+
+
+
+
+
+ RAM (GB)
+
+
setRam(Number(e.target.value))}
+ className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm text-white outline-none transition-colors focus:border-sky-500/50"
+ >
+ 128 GB
+ 64 GB
+ 32 GB
+ 16 GB
+
+
+
+
+
+ Number of GPUs
+
+
setNgpu(Number(e.target.value))}
+ className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm text-white outline-none transition-colors focus:border-sky-500/50"
+ >
+ 1 GPU
+ 2 GPUs
+ 4 GPUs
+
+
+
+
+
+ {/* Step 3: Output Mode */}
+
+
+ 3
+
Output mode
+
+
+ {TIERS.map(({ id, name, desc, badge }) => {
+ const tierConfig = CONFIGS[task][id]
+ return (
+
setTier(id)}
+ className={`group relative rounded-xl border p-4 text-left transition-all duration-200 ${
+ tier === id
+ ? "border-sky-500/50 bg-sky-500/10 shadow-lg shadow-sky-500/10"
+ : "border-white/5 bg-white/[0.02] hover:border-white/10 hover:bg-white/[0.04]"
+ }`}
+ >
+ {badge && (
+
+ {badge}
+
+ )}
+
+ {name}
+
+ {desc}
+
+
+
+ {tierConfig.time}
+
+
+
+ {tierConfig.vram}
+
+
+
+ )
+ })}
+
+ {warning && (
+
+ {warning}
+
+ )}
+
+
+ {/* Output Configuration */}
+
+
+ ✓
+
Your configuration
+
+
+ {/* Meta Cards */}
+
+
+
Model
+
{config.modelShort}
+
+
+
Est. Generation Time
+
{config.time}
+
+
+
Peak VRAM
+
{config.vram}
+
+
+ {/* Command Block */}
+
+
+ {copied ? : }
+ {copied ? "Copied" : "Copy"}
+
+
+ {command}
+
+
+
+
+
+
+ )
+}
diff --git a/consumer-guide/app/tuning/page.tsx b/consumer-guide/app/tuning/page.tsx
new file mode 100644
index 000000000..526002d23
--- /dev/null
+++ b/consumer-guide/app/tuning/page.tsx
@@ -0,0 +1,823 @@
+"use client"
+
+import { useState, useMemo } from "react"
+import {
+ Monitor, Cpu, Film, Sparkles, Zap, HardDrive, BarChart3,
+ Copy, Check, Info, ChevronDown, ChevronUp, Settings2,
+ Layers, Clock, Database, Gauge
+} from "lucide-react"
+import Link from "next/link"
+
+// Types
+interface Config {
+ // Category A - Model & Hardware
+ model_id: string
+ workload_type: string
+ gpu: string
+ num_gpus: number
+ // Category B - Video Spec
+ height: number
+ width: number
+ num_frames: number
+ fps: number
+ // Category C - Quality
+ num_inference_steps: number
+ guidance_scale: number
+ embedded_cfg_scale: number
+ flow_shift: number | null
+ seed: number
+ // Category D - Speed
+ attention_backend: string
+ VSA_sparsity: number
+ dmd_denoising_steps: string
+ // Category E - Memory
+ dit_cpu_offload: boolean
+ dit_layerwise_offload: boolean
+ vae_cpu_offload: boolean
+ text_encoder_cpu_offload: boolean
+ image_encoder_cpu_offload: boolean
+ pin_cpu_memory: boolean
+ use_fsdp_inference: boolean
+}
+
+const modelOptions = [
+ { id: "FastVideo/FastWan2.1-T2V-1.3B-Diffusers", name: "FastWan 1.3B (T2V)", size: "1.3B" },
+ { id: "Wan-AI/Wan2.1-T2V-14B-Diffusers", name: "Wan 14B (T2V)", size: "14B" },
+ { id: "Wan-AI/Wan2.1-I2V-14B-480P-Diffusers", name: "Wan 14B I2V 480P", size: "14B" },
+ { id: "Wan-AI/Wan2.1-I2V-14B-720P-Diffusers", name: "Wan 14B I2V 720P", size: "14B" },
+]
+
+const workloadOptions = [
+ { id: "t2v", name: "Text to Video", icon: Film },
+ { id: "i2v", name: "Image to Video", icon: Sparkles },
+ { id: "ti2v", name: "Text+Image to Video", icon: Layers },
+ { id: "action", name: "Action", icon: Zap },
+]
+
+const gpuOptions = [
+ { id: "rtx4090", name: "RTX 4090", vram: "24GB" },
+ { id: "rtx5090", name: "RTX 5090", vram: "32GB" },
+ { id: "a100", name: "A100", vram: "40/80GB" },
+ { id: "h100", name: "H100", vram: "80GB" },
+]
+
+const resolutionPresets = [
+ { height: 480, width: 832, label: "480p (832×480)" },
+ { height: 720, width: 1280, label: "720p (1280×720)" },
+ { height: 1080, width: 1920, label: "1080p (1920×1080)" },
+]
+
+const framePresets = [
+ { frames: 49, label: "49 frames (~2s @24fps)" },
+ { frames: 81, label: "81 frames (~3.4s @24fps)" },
+ { frames: 121, label: "121 frames (~5s @24fps)" },
+]
+
+const attentionBackends = [
+ { id: "FLASH_ATTN", name: "Flash Attention", desc: "Best quality-speed trade-off", speed: "Baseline" },
+ { id: "VIDEO_SPARSE_ATTN", name: "Video Sparse Attention", desc: "~30% faster, minimal quality loss", speed: "Fast" },
+ { id: "SAGE_ATTN", name: "Sage Attention", desc: "~40% faster, visible at high motion", speed: "Faster" },
+ { id: "SLA_ATTN", name: "Sliding Tile Attention", desc: "Check model compatibility", speed: "Fast" },
+]
+
+// Tooltip component
+function Tooltip({ content }: { content: string }) {
+ const [show, setShow] = useState(false)
+ return (
+
+
setShow(true)}
+ onMouseLeave={() => setShow(false)}
+ className="ml-1.5 text-slate-500 hover:text-sky-400 transition-colors"
+ >
+
+
+ {show && (
+
+ )}
+
+ )
+}
+
+// Collapsible section
+function Section({
+ title,
+ icon: Icon,
+ number,
+ children,
+ defaultOpen = true,
+ color = "sky"
+}: {
+ title: string
+ icon: React.ElementType
+ number: string
+ children: React.ReactNode
+ defaultOpen?: boolean
+ color?: "sky" | "emerald" | "amber" | "violet" | "rose" | "slate"
+}) {
+ const [open, setOpen] = useState(defaultOpen)
+ const colorClasses = {
+ sky: "bg-sky-500/20 text-sky-400",
+ emerald: "bg-emerald-500/20 text-emerald-400",
+ amber: "bg-amber-500/20 text-amber-400",
+ violet: "bg-violet-500/20 text-violet-400",
+ rose: "bg-rose-500/20 text-rose-400",
+ slate: "bg-slate-500/20 text-slate-400",
+ }
+ const textColors = {
+ sky: "text-sky-400",
+ emerald: "text-emerald-400",
+ amber: "text-amber-400",
+ violet: "text-violet-400",
+ rose: "text-rose-400",
+ slate: "text-slate-400",
+ }
+
+ return (
+
+
setOpen(!open)}
+ className="w-full flex items-center justify-between p-4 hover:bg-white/[0.02] transition-colors"
+ >
+
+
+ {number}
+
+
+
{title}
+
+ {open ? : }
+
+ {open &&
{children}
}
+
+ )
+}
+
+// Input components
+function NumberInput({
+ label,
+ value,
+ onChange,
+ min,
+ max,
+ step = 1,
+ tooltip,
+ unit
+}: {
+ label: string
+ value: number
+ onChange: (v: number) => void
+ min?: number
+ max?: number
+ step?: number
+ tooltip?: string
+ unit?: string
+}) {
+ return (
+
+
+ {label}
+ {tooltip && }
+
+
+ onChange(Number(e.target.value))}
+ min={min}
+ max={max}
+ step={step}
+ className="w-full rounded-lg border border-white/10 bg-slate-900/50 px-3 py-2.5 text-sm text-white focus:border-sky-500/50 focus:outline-none focus:ring-1 focus:ring-sky-500/50"
+ />
+ {unit && {unit} }
+
+
+ )
+}
+
+function SelectInput({
+ label,
+ value,
+ onChange,
+ options,
+ tooltip
+}: {
+ label: string
+ value: string
+ onChange: (v: string) => void
+ options: { id: string; name: string; desc?: string }[]
+ tooltip?: string
+}) {
+ return (
+
+
+ {label}
+ {tooltip && }
+
+ onChange(e.target.value)}
+ className="w-full rounded-lg border border-white/10 bg-slate-900/50 px-3 py-2.5 text-sm text-white focus:border-sky-500/50 focus:outline-none focus:ring-1 focus:ring-sky-500/50"
+ >
+ {options.map((opt) => (
+ {opt.name}
+ ))}
+
+
+ )
+}
+
+function Toggle({
+ label,
+ checked,
+ onChange,
+ tooltip,
+ disabled = false
+}: {
+ label: string
+ checked: boolean
+ onChange: (v: boolean) => void
+ tooltip?: string
+ disabled?: boolean
+}) {
+ return (
+
+
+ {label}
+ {tooltip && }
+
+ !disabled && onChange(!checked)}
+ className={`relative h-6 w-11 rounded-full transition-colors ${checked ? 'bg-sky-500' : 'bg-slate-700'}`}
+ >
+
+
+
+ )
+}
+
+export default function AdvancedTuningPage() {
+ const [copied, setCopied] = useState(false)
+ const [config, setConfig] = useState({
+ // Category A
+ model_id: "FastVideo/FastWan2.1-T2V-1.3B-Diffusers",
+ workload_type: "t2v",
+ gpu: "rtx4090",
+ num_gpus: 1,
+ // Category B
+ height: 480,
+ width: 832,
+ num_frames: 49,
+ fps: 24,
+ // Category C
+ num_inference_steps: 30,
+ guidance_scale: 5.0,
+ embedded_cfg_scale: 6.0,
+ flow_shift: null,
+ seed: -1,
+ // Category D
+ attention_backend: "FLASH_ATTN",
+ VSA_sparsity: 0,
+ dmd_denoising_steps: "1000,757,522",
+ // Category E
+ dit_cpu_offload: false,
+ dit_layerwise_offload: false,
+ vae_cpu_offload: false,
+ text_encoder_cpu_offload: false,
+ image_encoder_cpu_offload: false,
+ pin_cpu_memory: false,
+ use_fsdp_inference: false,
+ })
+
+ // Derived values
+ const videoLength = useMemo(() => (config.num_frames / config.fps).toFixed(1), [config.num_frames, config.fps])
+ const totalPixels = useMemo(() => config.height * config.width * config.num_frames, [config.height, config.width, config.num_frames])
+ const computeCost = useMemo(() => totalPixels * config.num_inference_steps, [totalPixels, config.num_inference_steps])
+
+ // Estimate generation time (rough approximation)
+ const estimatedTime = useMemo(() => {
+ const baseTime = (totalPixels / 19600000) * 60 * (config.num_inference_steps / 30)
+ let multiplier = 1
+ if (config.dit_cpu_offload) multiplier *= 2.5
+ if (config.dit_layerwise_offload) multiplier *= 4
+ if (config.attention_backend === "VIDEO_SPARSE_ATTN") multiplier *= 0.7
+ if (config.attention_backend === "SAGE_ATTN") multiplier *= 0.6
+ return Math.round(baseTime * multiplier)
+ }, [totalPixels, config.num_inference_steps, config.dit_cpu_offload, config.dit_layerwise_offload, config.attention_backend])
+
+ // Generate command
+ const command = useMemo(() => {
+ const lines = [
+ "# 1. Install FastVideo",
+ "pip install fastvideo",
+ "",
+ "# 2. Download model",
+ `huggingface-cli download ${config.model_id} \\`,
+ ` --local-dir ./models/${config.model_id.split('/').pop()}`,
+ "",
+ "# 3. Run generation",
+ "fastvideo generate \\",
+ ` --model ./models/${config.model_id.split('/').pop()} \\`,
+ ` --attention ${config.attention_backend} \\`,
+ ` --height ${config.height} --width ${config.width} \\`,
+ ` --num_frames ${config.num_frames} \\`,
+ ` --num_inference_steps ${config.num_inference_steps} \\`,
+ ` --guidance_scale ${config.guidance_scale} \\`,
+ ]
+
+ if (config.embedded_cfg_scale !== 6.0) {
+ lines.push(` --embedded_cfg_scale ${config.embedded_cfg_scale} \\`)
+ }
+ if (config.flow_shift !== null) {
+ lines.push(` --flow_shift ${config.flow_shift} \\`)
+ }
+ if (config.seed !== -1) {
+ lines.push(` --seed ${config.seed} \\`)
+ }
+ if (config.attention_backend === "VIDEO_SPARSE_ATTN" && config.VSA_sparsity > 0) {
+ lines.push(` --vsa_sparsity ${config.VSA_sparsity} \\`)
+ }
+ if (config.dit_cpu_offload) lines.push(" --dit_cpu_offload \\")
+ if (config.dit_layerwise_offload) lines.push(" --dit_layerwise_offload \\")
+ if (config.vae_cpu_offload) lines.push(" --vae_cpu_offload \\")
+ if (config.text_encoder_cpu_offload) lines.push(" --text_encoder_cpu_offload \\")
+ if (config.image_encoder_cpu_offload && (config.workload_type === "i2v" || config.workload_type === "ti2v")) {
+ lines.push(" --image_encoder_cpu_offload \\")
+ }
+ if (config.pin_cpu_memory) lines.push(" --pin_cpu_memory \\")
+ if (config.use_fsdp_inference && config.num_gpus > 1) lines.push(" --use_fsdp_inference \\")
+
+ if (config.workload_type === "i2v" || config.workload_type === "ti2v") {
+ lines.push(" --image ./input.png \\")
+ }
+ lines.push(' --prompt "your prompt here"')
+
+ return lines.join("\n")
+ }, [config])
+
+ const copyCommand = () => {
+ navigator.clipboard.writeText(command)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ }
+
+ const updateConfig = (key: K, value: Config[K]) => {
+ setConfig((prev) => ({ ...prev, [key]: value }))
+ }
+
+ const isFastWanDMD = config.model_id.includes("FastWan")
+ const isI2V = config.workload_type === "i2v" || config.workload_type === "ti2v"
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
+
+
+
Advanced Tuning Guide
+
RTX 4090 / RTX 5090 - All 27 Parameters
+
+
+
+ Quick Start
+
+
+
+
+
+
+ {/* Stats Bar */}
+
+
+
+
+
+ Video Length
+
+
{videoLength}s
+
+
+
+
+ Est. Time
+
+
~{estimatedTime}s
+
+
+
+
+ Total Pixels
+
+
{(totalPixels / 1000000).toFixed(1)}M
+
+
+
+
+ Compute Cost
+
+
{(computeCost / 1000000000).toFixed(2)}G
+
+
+
+
+
+ {/* Left Column - Parameters */}
+
+ {/* Category A - Model & Hardware */}
+
+
+
+ updateConfig("model_id", v)}
+ options={modelOptions.map(m => ({ id: m.id, name: `${m.name} (${m.size})` }))}
+ tooltip="HuggingFace model path - uniquely identifies the model weights and architecture."
+ />
+
+
updateConfig("workload_type", v)}
+ options={workloadOptions.map(w => ({ id: w.id, name: w.name }))}
+ tooltip="Task category. Determines which pipeline stages run."
+ />
+ updateConfig("gpu", v)}
+ options={gpuOptions.map(g => ({ id: g.id, name: `${g.name} (${g.vram})` }))}
+ tooltip="GPU model. Used for benchmark tracking and hardware-aware configuration."
+ />
+ updateConfig("num_gpus", v)}
+ min={1}
+ max={8}
+ tooltip="Values > 1 require use_fsdp_inference = true."
+ />
+
+
+
+ {/* Category B - Video Spec */}
+
+
+
+
Resolution Preset
+
+ {resolutionPresets.map((preset) => (
+ {
+ updateConfig("height", preset.height)
+ updateConfig("width", preset.width)
+ }}
+ className={`rounded-lg border px-3 py-2 text-sm transition-all ${
+ config.height === preset.height && config.width === preset.width
+ ? "border-emerald-500/50 bg-emerald-500/10 text-emerald-400"
+ : "border-white/10 bg-white/[0.02] text-slate-400 hover:border-white/20"
+ }`}
+ >
+ {preset.label}
+
+ ))}
+
+
+
+ updateConfig("height", v)}
+ min={256}
+ max={2160}
+ step={8}
+ unit="px"
+ tooltip="Frame height in pixels. Must be divisible by 8 (VAE requirement)."
+ />
+ updateConfig("width", v)}
+ min={256}
+ max={3840}
+ step={8}
+ unit="px"
+ tooltip="Frame width in pixels. Must be divisible by 8."
+ />
+
+
+
Frame Count Preset
+
+ {framePresets.map((preset) => (
+ updateConfig("num_frames", preset.frames)}
+ className={`rounded-lg border px-3 py-2 text-sm transition-all ${
+ config.num_frames === preset.frames
+ ? "border-emerald-500/50 bg-emerald-500/10 text-emerald-400"
+ : "border-white/10 bg-white/[0.02] text-slate-400 hover:border-white/20"
+ }`}
+ >
+ {preset.label}
+
+ ))}
+
+
+
+ updateConfig("num_frames", v)}
+ min={1}
+ max={240}
+ tooltip="Total number of frames to generate. Directly sets video duration at a given fps."
+ />
+ updateConfig("fps", v)}
+ min={1}
+ max={60}
+ tooltip="Frames per second for the output video. Does NOT affect generation time."
+ />
+
+
+
+
+ {/* Category C - Quality */}
+
+
+
+
+ Inference Steps
+
+
+
+ updateConfig("num_inference_steps", Number(e.target.value))}
+ className="flex-1 h-2 rounded-full bg-slate-700 appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-amber-400"
+ />
+ {config.num_inference_steps}
+
+
+ Draft (10)
+ Balanced (30)
+ High (80)
+
+
+
+
+ Guidance Scale
+
+
+
+ updateConfig("guidance_scale", Number(e.target.value))}
+ className="flex-1 h-2 rounded-full bg-slate-700 appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-amber-400"
+ />
+ {config.guidance_scale}
+
+
+ Loose (3)
+ Balanced (5)
+ Strict (8+)
+
+
+
+ updateConfig("embedded_cfg_scale", v)}
+ min={1}
+ max={10}
+ step={0.5}
+ tooltip="Model-level CFG signal passed into the DiT. Default: 6.0. Rarely change."
+ />
+ updateConfig("seed", v)}
+ min={-1}
+ tooltip="Random seed. Set to -1 for random. Fix a seed for reproducibility."
+ />
+
+
+
+
+ {/* Category D - Speed */}
+
+
+
+
+ Attention Backend
+
+
+ {attentionBackends.map((backend) => (
+
updateConfig("attention_backend", backend.id)}
+ className={`rounded-lg border p-3 text-left transition-all ${
+ config.attention_backend === backend.id
+ ? "border-violet-500/50 bg-violet-500/10"
+ : "border-white/10 bg-white/[0.02] hover:border-white/20"
+ }`}
+ >
+
+
+ {backend.name}
+
+
+ {backend.speed}
+
+
+ {backend.desc}
+
+ ))}
+
+
+ {config.attention_backend === "VIDEO_SPARSE_ATTN" && (
+
+
+ VSA Sparsity
+
+
+
+ updateConfig("VSA_sparsity", Number(e.target.value))}
+ className="flex-1 h-2 rounded-full bg-slate-700 appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-violet-400"
+ />
+ {config.VSA_sparsity}
+
+
+ )}
+ {isFastWanDMD && config.gpu === "rtx4090" && (
+
+
+
+ DMD Mode Available
+
+
+ FastWan DMD uses 3-step distillation for ~10x faster generation.
+
+
+ dmd_steps: {config.dmd_denoising_steps}
+
+
+ )}
+
+
+
+ {/* Category E - Memory */}
+
+
+
+
+ Tip: For 24GB VRAM with 14B models, enable dit_cpu_offload + vae_cpu_offload + pin_cpu_memory.
+
+
+
updateConfig("dit_cpu_offload", v)}
+ tooltip="Offload entire DiT to RAM. Saves ~4GB, ~2-3x slower."
+ />
+ updateConfig("dit_layerwise_offload", v)}
+ tooltip="Layer-by-layer offload. Maximum VRAM saving (~8GB), ~4x slower."
+ />
+ updateConfig("vae_cpu_offload", v)}
+ tooltip="Offload VAE decoder. Saves ~2GB. Minimal time penalty."
+ />
+ updateConfig("text_encoder_cpu_offload", v)}
+ tooltip="Offload text encoder. Saves ~1-2GB. Runs once at start."
+ />
+ updateConfig("image_encoder_cpu_offload", v)}
+ tooltip="I2V/TI2V only. Saves ~1-2GB."
+ disabled={!isI2V}
+ />
+ updateConfig("pin_cpu_memory", v)}
+ tooltip="Pins RAM pages for faster PCIe DMA transfers. Always enable with any offload flag."
+ />
+ updateConfig("use_fsdp_inference", v)}
+ tooltip="Shards model weights across multiple GPUs via FSDP."
+ disabled={config.num_gpus <= 1}
+ />
+
+
+
+
+ {/* Right Column - Output */}
+
+
+ {/* Generated Command */}
+
+
+
+
+
+
+
Generated Command
+
+
+ {copied ? : }
+ {copied ? "Copied" : "Copy"}
+
+
+
+
+
+ {/* Quick Reference */}
+
+
Quick Reference
+
+
+ Resolution
+ {config.width}x{config.height}
+
+
+ Frames
+ {config.num_frames}
+
+
+ Steps
+ {config.num_inference_steps}
+
+
+ Backend
+ {config.attention_backend}
+
+
+ Offloading
+
+ {[config.dit_cpu_offload && "DiT", config.vae_cpu_offload && "VAE"].filter(Boolean).join("+") || "None"}
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/consumer-guide/components.json b/consumer-guide/components.json
new file mode 100644
index 000000000..4ee62ee10
--- /dev/null
+++ b/consumer-guide/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/consumer-guide/components/theme-provider.tsx b/consumer-guide/components/theme-provider.tsx
new file mode 100644
index 000000000..55c2f6eb6
--- /dev/null
+++ b/consumer-guide/components/theme-provider.tsx
@@ -0,0 +1,11 @@
+'use client'
+
+import * as React from 'react'
+import {
+ ThemeProvider as NextThemesProvider,
+ type ThemeProviderProps,
+} from 'next-themes'
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children}
+}
diff --git a/consumer-guide/components/ui/accordion.tsx b/consumer-guide/components/ui/accordion.tsx
new file mode 100644
index 000000000..e538a33b9
--- /dev/null
+++ b/consumer-guide/components/ui/accordion.tsx
@@ -0,0 +1,66 @@
+'use client'
+
+import * as React from 'react'
+import * as AccordionPrimitive from '@radix-ui/react-accordion'
+import { ChevronDownIcon } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+
+function Accordion({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function AccordionItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ svg]:rotate-180',
+ className,
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+ )
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+ )
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/consumer-guide/components/ui/alert-dialog.tsx b/consumer-guide/components/ui/alert-dialog.tsx
new file mode 100644
index 000000000..970445266
--- /dev/null
+++ b/consumer-guide/components/ui/alert-dialog.tsx
@@ -0,0 +1,157 @@
+'use client'
+
+import * as React from 'react'
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+
+import { cn } from '@/lib/utils'
+import { buttonVariants } from '@/components/ui/button'
+
+function AlertDialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function AlertDialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ )
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function AlertDialogFooter({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function AlertDialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogAction({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogCancel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/consumer-guide/components/ui/alert.tsx b/consumer-guide/components/ui/alert.tsx
new file mode 100644
index 000000000..e6751abe6
--- /dev/null
+++ b/consumer-guide/components/ui/alert.tsx
@@ -0,0 +1,66 @@
+import * as React from 'react'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+
+const alertVariants = cva(
+ 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
+ {
+ variants: {
+ variant: {
+ default: 'bg-card text-card-foreground',
+ destructive:
+ 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+)
+
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<'div'> & VariantProps) {
+ return (
+
+ )
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/consumer-guide/components/ui/aspect-ratio.tsx b/consumer-guide/components/ui/aspect-ratio.tsx
new file mode 100644
index 000000000..40bb1208d
--- /dev/null
+++ b/consumer-guide/components/ui/aspect-ratio.tsx
@@ -0,0 +1,11 @@
+'use client'
+
+import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'
+
+function AspectRatio({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+export { AspectRatio }
diff --git a/consumer-guide/components/ui/avatar.tsx b/consumer-guide/components/ui/avatar.tsx
new file mode 100644
index 000000000..aa98465a3
--- /dev/null
+++ b/consumer-guide/components/ui/avatar.tsx
@@ -0,0 +1,53 @@
+'use client'
+
+import * as React from 'react'
+import * as AvatarPrimitive from '@radix-ui/react-avatar'
+
+import { cn } from '@/lib/utils'
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/consumer-guide/components/ui/badge.tsx b/consumer-guide/components/ui/badge.tsx
new file mode 100644
index 000000000..fc4126b7a
--- /dev/null
+++ b/consumer-guide/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+
+const badgeVariants = cva(
+ 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
+ {
+ variants: {
+ variant: {
+ default:
+ 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
+ secondary:
+ 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
+ destructive:
+ 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+)
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'span'> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : 'span'
+
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/consumer-guide/components/ui/breadcrumb.tsx b/consumer-guide/components/ui/breadcrumb.tsx
new file mode 100644
index 000000000..1750ff26a
--- /dev/null
+++ b/consumer-guide/components/ui/breadcrumb.tsx
@@ -0,0 +1,109 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { ChevronRight, MoreHorizontal } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+
+function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
+ return
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
+ return (
+
+ )
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
+ return (
+
+ )
+}
+
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+}: React.ComponentProps<'a'> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot : 'a'
+
+ return (
+
+ )
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
+ return (
+
+ )
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<'li'>) {
+ return (
+ svg]:size-3.5', className)}
+ {...props}
+ >
+ {children ?? }
+
+ )
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<'span'>) {
+ return (
+
+
+ More
+
+ )
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/consumer-guide/components/ui/button-group.tsx b/consumer-guide/components/ui/button-group.tsx
new file mode 100644
index 000000000..09d443097
--- /dev/null
+++ b/consumer-guide/components/ui/button-group.tsx
@@ -0,0 +1,83 @@
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+import { Separator } from '@/components/ui/separator'
+
+const buttonGroupVariants = cva(
+ "flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ '[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none',
+ vertical:
+ 'flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none',
+ },
+ },
+ defaultVariants: {
+ orientation: 'horizontal',
+ },
+ },
+)
+
+function ButtonGroup({
+ className,
+ orientation,
+ ...props
+}: React.ComponentProps<'div'> & VariantProps) {
+ return (
+
+ )
+}
+
+function ButtonGroupText({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'div'> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot : 'div'
+
+ return (
+
+ )
+}
+
+function ButtonGroupSeparator({
+ className,
+ orientation = 'vertical',
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+ buttonGroupVariants,
+}
diff --git a/consumer-guide/components/ui/button.tsx b/consumer-guide/components/ui/button.tsx
new file mode 100644
index 000000000..f64632d15
--- /dev/null
+++ b/consumer-guide/components/ui/button.tsx
@@ -0,0 +1,60 @@
+import * as React from 'react'
+import { Slot } from '@radix-ui/react-slot'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
+ destructive:
+ 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
+ secondary:
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
+ ghost:
+ 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
+ link: 'text-primary underline-offset-4 hover:underline',
+ },
+ size: {
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
+ icon: 'size-9',
+ 'icon-sm': 'size-8',
+ 'icon-lg': 'size-10',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ },
+)
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'button'> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot : 'button'
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/consumer-guide/components/ui/calendar.tsx b/consumer-guide/components/ui/calendar.tsx
new file mode 100644
index 000000000..eaa373e25
--- /dev/null
+++ b/consumer-guide/components/ui/calendar.tsx
@@ -0,0 +1,213 @@
+'use client'
+
+import * as React from 'react'
+import {
+ ChevronDownIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+} from 'lucide-react'
+import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker'
+
+import { cn } from '@/lib/utils'
+import { Button, buttonVariants } from '@/components/ui/button'
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = 'label',
+ buttonVariant = 'ghost',
+ formatters,
+ components,
+ ...props
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps['variant']
+}) {
+ const defaultClassNames = getDefaultClassNames()
+
+ return (
+ svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className,
+ )}
+ captionLayout={captionLayout}
+ formatters={{
+ formatMonthDropdown: (date) =>
+ date.toLocaleString('default', { month: 'short' }),
+ ...formatters,
+ }}
+ classNames={{
+ root: cn('w-fit', defaultClassNames.root),
+ months: cn(
+ 'flex gap-4 flex-col md:flex-row relative',
+ defaultClassNames.months,
+ ),
+ month: cn('flex flex-col w-full gap-4', defaultClassNames.month),
+ nav: cn(
+ 'flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between',
+ defaultClassNames.nav,
+ ),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
+ defaultClassNames.button_previous,
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
+ defaultClassNames.button_next,
+ ),
+ month_caption: cn(
+ 'flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)',
+ defaultClassNames.month_caption,
+ ),
+ dropdowns: cn(
+ 'w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5',
+ defaultClassNames.dropdowns,
+ ),
+ dropdown_root: cn(
+ 'relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md',
+ defaultClassNames.dropdown_root,
+ ),
+ dropdown: cn(
+ 'absolute bg-popover inset-0 opacity-0',
+ defaultClassNames.dropdown,
+ ),
+ caption_label: cn(
+ 'select-none font-medium',
+ captionLayout === 'label'
+ ? 'text-sm'
+ : 'rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5',
+ defaultClassNames.caption_label,
+ ),
+ table: 'w-full border-collapse',
+ weekdays: cn('flex', defaultClassNames.weekdays),
+ weekday: cn(
+ 'text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none',
+ defaultClassNames.weekday,
+ ),
+ week: cn('flex w-full mt-2', defaultClassNames.week),
+ week_number_header: cn(
+ 'select-none w-(--cell-size)',
+ defaultClassNames.week_number_header,
+ ),
+ week_number: cn(
+ 'text-[0.8rem] select-none text-muted-foreground',
+ defaultClassNames.week_number,
+ ),
+ day: cn(
+ 'relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none',
+ defaultClassNames.day,
+ ),
+ range_start: cn(
+ 'rounded-l-md bg-accent',
+ defaultClassNames.range_start,
+ ),
+ range_middle: cn('rounded-none', defaultClassNames.range_middle),
+ range_end: cn('rounded-r-md bg-accent', defaultClassNames.range_end),
+ today: cn(
+ 'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',
+ defaultClassNames.today,
+ ),
+ outside: cn(
+ 'text-muted-foreground aria-selected:text-muted-foreground',
+ defaultClassNames.outside,
+ ),
+ disabled: cn(
+ 'text-muted-foreground opacity-50',
+ defaultClassNames.disabled,
+ ),
+ hidden: cn('invisible', defaultClassNames.hidden),
+ ...classNames,
+ }}
+ components={{
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ )
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === 'left') {
+ return (
+
+ )
+ }
+
+ if (orientation === 'right') {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+
+ )
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ )
+}
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ ...props
+}: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames()
+
+ const ref = React.useRef(null)
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus()
+ }, [modifiers.focused])
+
+ return (
+ span]:text-xs [&>span]:opacity-70',
+ defaultClassNames.day,
+ className,
+ )}
+ {...props}
+ />
+ )
+}
+
+export { Calendar, CalendarDayButton }
diff --git a/consumer-guide/components/ui/card.tsx b/consumer-guide/components/ui/card.tsx
new file mode 100644
index 000000000..db7dd3cc9
--- /dev/null
+++ b/consumer-guide/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from 'react'
+
+import { cn } from '@/lib/utils'
+
+function Card({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ )
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+}
diff --git a/consumer-guide/components/ui/carousel.tsx b/consumer-guide/components/ui/carousel.tsx
new file mode 100644
index 000000000..d4a768e7a
--- /dev/null
+++ b/consumer-guide/components/ui/carousel.tsx
@@ -0,0 +1,241 @@
+'use client'
+
+import * as React from 'react'
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from 'embla-carousel-react'
+import { ArrowLeft, ArrowRight } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+import { Button } from '@/components/ui/button'
+
+type CarouselApi = UseEmblaCarouselType[1]
+type UseCarouselParameters = Parameters
+type CarouselOptions = UseCarouselParameters[0]
+type CarouselPlugin = UseCarouselParameters[1]
+
+type CarouselProps = {
+ opts?: CarouselOptions
+ plugins?: CarouselPlugin
+ orientation?: 'horizontal' | 'vertical'
+ setApi?: (api: CarouselApi) => void
+}
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0]
+ api: ReturnType[1]
+ scrollPrev: () => void
+ scrollNext: () => void
+ canScrollPrev: boolean
+ canScrollNext: boolean
+} & CarouselProps
+
+const CarouselContext = React.createContext(null)
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext)
+
+ if (!context) {
+ throw new Error('useCarousel must be used within a ')
+ }
+
+ return context
+}
+
+function Carousel({
+ orientation = 'horizontal',
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+}: React.ComponentProps<'div'> & CarouselProps) {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === 'horizontal' ? 'x' : 'y',
+ },
+ plugins,
+ )
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) return
+ setCanScrollPrev(api.canScrollPrev())
+ setCanScrollNext(api.canScrollNext())
+ }, [])
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev()
+ }, [api])
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext()
+ }, [api])
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === 'ArrowLeft') {
+ event.preventDefault()
+ scrollPrev()
+ } else if (event.key === 'ArrowRight') {
+ event.preventDefault()
+ scrollNext()
+ }
+ },
+ [scrollPrev, scrollNext],
+ )
+
+ React.useEffect(() => {
+ if (!api || !setApi) return
+ setApi(api)
+ }, [api, setApi])
+
+ React.useEffect(() => {
+ if (!api) return
+ onSelect(api)
+ api.on('reInit', onSelect)
+ api.on('select', onSelect)
+
+ return () => {
+ api?.off('select', onSelect)
+ }
+ }, [api, onSelect])
+
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+function CarouselContent({ className, ...props }: React.ComponentProps<'div'>) {
+ const { carouselRef, orientation } = useCarousel()
+
+ return (
+
+ )
+}
+
+function CarouselItem({ className, ...props }: React.ComponentProps<'div'>) {
+ const { orientation } = useCarousel()
+
+ return (
+
+ )
+}
+
+function CarouselPrevious({
+ className,
+ variant = 'outline',
+ size = 'icon',
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+
+ return (
+
+
+ Previous slide
+
+ )
+}
+
+function CarouselNext({
+ className,
+ variant = 'outline',
+ size = 'icon',
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
+
+ return (
+
+
+ Next slide
+
+ )
+}
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+}
diff --git a/consumer-guide/components/ui/chart.tsx b/consumer-guide/components/ui/chart.tsx
new file mode 100644
index 000000000..56225f8e9
--- /dev/null
+++ b/consumer-guide/components/ui/chart.tsx
@@ -0,0 +1,351 @@
+'use client'
+
+import * as React from 'react'
+import * as RechartsPrimitive from 'recharts'
+
+import { cn } from '@/lib/utils'
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: '', dark: '.dark' } as const
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode
+ icon?: React.ComponentType
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ )
+}
+
+type ChartContextProps = {
+ config: ChartConfig
+}
+
+const ChartContext = React.createContext(null)
+
+function useChart() {
+ const context = React.useContext(ChartContext)
+
+ if (!context) {
+ throw new Error('useChart must be used within a ')
+ }
+
+ return context
+}
+
+function ChartContainer({
+ id,
+ className,
+ children,
+ config,
+ ...props
+}: React.ComponentProps<'div'> & {
+ config: ChartConfig
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >['children']
+}) {
+ const uniqueId = React.useId()
+ const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ )
+}
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([, config]) => config.theme || config.color,
+ )
+
+ if (!colorConfig.length) {
+ return null
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/consumer-guide/public/placeholder-logo.svg b/consumer-guide/public/placeholder-logo.svg
new file mode 100644
index 000000000..b1695aafc
--- /dev/null
+++ b/consumer-guide/public/placeholder-logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/consumer-guide/public/placeholder.svg b/consumer-guide/public/placeholder.svg
new file mode 100644
index 000000000..e763910b2
--- /dev/null
+++ b/consumer-guide/public/placeholder.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/consumer-guide/styles/globals.css b/consumer-guide/styles/globals.css
new file mode 100644
index 000000000..dc2aea17f
--- /dev/null
+++ b/consumer-guide/styles/globals.css
@@ -0,0 +1,125 @@
+@import 'tailwindcss';
+@import 'tw-animate-css';
+
+@custom-variant dark (&:is(.dark *));
+
+:root {
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --destructive-foreground: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --radius: 0.625rem;
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.145 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.145 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.985 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.396 0.141 25.723);
+ --destructive-foreground: oklch(0.637 0.237 25.331);
+ --border: oklch(0.269 0 0);
+ --input: oklch(0.269 0 0);
+ --ring: oklch(0.439 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(0.269 0 0);
+ --sidebar-ring: oklch(0.439 0 0);
+}
+
+@theme inline {
+ --font-sans: 'Geist', 'Geist Fallback';
+ --font-mono: 'Geist Mono', 'Geist Mono Fallback';
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/consumer-guide/tsconfig.json b/consumer-guide/tsconfig.json
new file mode 100644
index 000000000..48d6d8209
--- /dev/null
+++ b/consumer-guide/tsconfig.json
@@ -0,0 +1,41 @@
+{
+ "compilerOptions": {
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "target": "ES6",
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": [
+ "./*"
+ ]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}