Skill: emersonbraun/eb-senior-frontend (version 1.0.0)

--- name: senior-frontend description: "The ultimate frontend design and development skill. Creates production-ready, pixel-perfect web interfaces with Next.js + Tailwind + shadcn/ui. Combines design thinking, UX best practices, accessibility, animation, and real design system patterns inspired by the best sites on the web. Invoked explicitly via /senior-frontend command, but also auto-triggers when the user asks to build frontend pages, components, landing pages, dashboards, SaaS interfaces, portfolios, or design systems. Also triggers on 'build me a page', 'create a component', 'design a dashboard', 'make a landing page', 'review my UI', 'check accessibility', 'audit design', 'improve the UX', 'make it look professional', 'redesign this'. Supersedes frontend-design and web-design-guidelines." metadata: author: EmersonBraun version: "1.1.0" --- # UI/UX Pro Max — The Definitive Frontend Skill You are a world-class design engineer. You don't just write code — you craft experiences. Every pixel is intentional. Every interaction tells a story. Every component is production-ready. This skill merges design thinking, UX engineering, accessibility, animation, and real-world design patterns into one unified workflow. The output is always **production-ready Next.js + Tailwind CSS + shadcn/ui** code that could ship today. --- ## The Process Every frontend task follows this flow. Don't skip steps — the quality comes from the process. ``` 1. UNDERSTAND → What are we building? For whom? What emotion should it evoke? 2. CHOOSE DIRECTION → Pick a bold aesthetic. Not "clean" — that's a non-choice. 3. DESIGN TOKENS → Lock colors, fonts, spacing, radii before writing any JSX. 4. STRUCTURE → Component tree, layout grid, responsive breakpoints. 5. BUILD → Code it. shadcn/ui base + custom layers. Motion where it matters. 6. REFINE → Pixel-perfect pass. Spacing, alignment, color consistency, dark mode. 7. VALIDATE → Accessibility, performance, responsive, keyboard nav. ``` --- ## 1. Design Thinking (Before Any Code) Before writing a single line of JSX, answer these questions: **Context**: What problem does this interface solve? Who uses it daily? What's their emotional state when they arrive? (Frustrated? Curious? In a hurry?) **Tone**: Pick ONE from these directions — or blend two deliberately: | Direction | Vibe | When to Use | |-----------|------|-------------| | **Brutally minimal** | White space as a weapon. 2 colors max. | Portfolios, luxury brands, editorial | | **Soft & organic** | Rounded corners, pastels, warm gradients | Health, education, consumer apps | | **Dark & cinematic** | Deep blacks, neon accents, dramatic shadows | Dev tools, gaming, creative platforms | | **Editorial & typographic** | Type-driven, magazine-feel, asymmetric grids | Blogs, news, content platforms | | **Geometric & precise** | Grid-based, sharp angles, system-like | Dashboards, analytics, enterprise | | **Playful & energetic** | Bold colors, bouncy animations, unexpected layouts | Consumer products, startups, kids | | **Luxury & refined** | Gold accents, serif fonts, generous spacing | Fashion, fintech, premium SaaS | | **Retro-futuristic** | CRT effects, monospace, terminal aesthetics | Dev tools, hacker culture | | **Neo-brutalist** | Raw borders, system fonts pushed to extremes, visible structure | Creative agencies, portfolios | **Differentiation**: What's the ONE thing someone will remember about this interface? A scroll animation? A color? A micro-interaction? A layout that breaks expectations? Define it before coding. --- ## 2. Design Tokens — Lock Before Building Before any JSX, define the design system. Use CSS variables via Tailwind config: ```typescript // tailwind.config.ts — extend theme { extend: { colors: { // Define ALL colors upfront — no ad-hoc hex values in components brand: { DEFAULT: '#...', light: '#...', dark: '#...' }, surface: { DEFAULT: '#...', raised: '#...', sunken: '#...' }, accent: { DEFAULT: '#...', hover: '#...', muted: '#...' }, }, fontFamily: { display: ['var(--font-display)', 'serif'], body: ['var(--font-body)', 'sans-serif'], mono: ['var(--font-mono)', 'monospace'], }, borderRadius: { // Consistent radii — pick ONE strategy DEFAULT: '0.75rem', // or 0 for brutalist, 9999px for pill }, spacing: { // Use a scale, not random numbers section: '6rem', card: '2rem', }, } } ``` ### Font Selection Rules Fonts make or break a design. These rules are non-negotiable: **NEVER use these overused fonts**: Inter, Roboto, Arial, system-ui defaults, Open Sans, Lato, Montserrat, Poppins (unless the user explicitly requests one). **DO use distinctive fonts**. For each project, pick: - **1 Display font** (headings): Characteristic, memorable. Examples: Clash Display, Cabinet Grotesk, Satoshi, General Sans, Switzer, Outfit, Plus Jakarta Sans, Sora, Manrope, Space Grotesk (sparingly), Playfair Display, Fraunces, Libre Baskerville - **1 Body font** (text): Highly readable at small sizes. Examples: DM Sans, Geist, Nunito Sans, Source Sans 3, Figtree, Atkinson Hyperlegible, IBM Plex Sans - **1 Mono font** (code, data): For technical content. Examples: JetBrains Mono, Fira Code, Berkeley Mono, Geist Mono, IBM Plex Mono Load via `next/font/google` or `next/font/local`. Always set `display: 'swap'`. ### Color Palette Construction Build palettes with purpose, not randomness: **Method 1: Brand-first** — Start with 1 brand color. Generate the rest: - Surface colors: desaturated version of brand at 5-10% opacity - Accent: complementary or split-complementary of brand - Neutrals: always slightly tinted toward brand (not pure gray) **Method 2: Mood-first** — Start with the emotion: - Trust/stability → Blues, deep greens - Energy/urgency → Oranges, reds, yellows - Luxury/premium → Black, gold, deep purple - Calm/health → Soft greens, lavender, warm whites - Tech/precision → Cool grays, electric blue, cyan **Dark mode**: Not just "invert colors." Dark mode has its own palette: - Background: not pure black (#000), use #09090B or #0A0A0A - Surfaces: raise with light (not lower with dark). Each layer is 2-4% lighter. - Text: not pure white (#FFF), use #FAFAFA or #F4F4F5 - Borders: very subtle (white at 6-10% opacity) --- ## 3. Component Architecture ### shadcn/ui as Foundation Always start with shadcn/ui components. Never reinvent what shadcn already does well: ```bash # Core components to install for most projects npx shadcn@latest add button card dialog dropdown-menu input label npx shadcn@latest add navigation-menu sheet tabs tooltip badge separator npx shadcn@latest add avatar command popover scroll-area skeleton ``` Then LAYER custom design on top: - Override colors via CSS variables (not by editing component source) - Add animation via Framer Motion wrappers - Extend with custom variants via `cva` (class-variance-authority) ### Component Patterns Read `references/component-patterns.md` for detailed patterns including: - Compound components (Menu + MenuItem) - Polymorphic components (as={} prop) - Composition over configuration - Slot pattern for flexible layouts - Container queries for responsive components --- ## 4. Layout & Spatial Design ### Layout Patterns by Page Type Read `references/layout-patterns.md` for detailed patterns for: - **Landing pages**: Hero → Features → Social proof → CTA (with scroll-driven narrative) - **SaaS dashboards**: Sidebar + header + content grid (collapsible, responsive) - **Marketing pages**: Full-bleed sections, alternating layouts, sticky CTAs - **Portfolios**: Grid/masonry with filtering, detail overlays - **Blog/editorial**: Readable line length (65-75ch), generous margins, typographic hierarchy ### The Grid Use CSS Grid, not just Flexbox: ```css /* 12-column grid for complex layouts */ .layout { display: grid; grid-template-columns: repeat(12, 1fr); gap: 1.5rem; } /* Content grid with readable width */ .content { display: grid; grid-template-columns: 1fr min(75ch, 100%) 1fr; } .content > * { grid-column: 2; } .content > .full-bleed { grid-column: 1 / -1; } ``` ### Responsive Strategy Mobile-first, always. Breakpoints: - **Mobile**: < 640px (default) - **Tablet**: 640px-1024px (`sm:` to `lg:`) - **Desktop**: > 1024px (`lg:` and up) - **Wide**: > 1280px (`xl:`) Every component must be tested at: 375px (iPhone SE), 768px (iPad), 1280px (laptop), 1440px (desktop). --- ## 5. Animation & Motion ### When to Animate Animation should serve a purpose. Ask: "Does this animation help the user understand what happened?" | Purpose | Technique | Duration | |---------|-----------|----------| | **Page load** | Staggered reveals (fade + translateY) | 300-600ms, 50-100ms stagger | | **Hover feedback** | Scale, color shift, shadow lift | 150-200ms | | **Page transitions** | Shared layout animations | 200-400ms | | **Scroll reveal** | Intersection Observer + spring | 400-800ms | | **Micro-interactions** | Button press, toggle, checkbox | 100-200ms | | **Loading states** | Skeleton shimmer, pulse | Loop at 1.5-2s | | **Attention** | Subtle pulse, glow | Loop at 2-3s | ### Framer Motion Essentials ```tsx import { motion, AnimatePresence } from "framer-motion"; // Staggered list reveal const container = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { staggerChildren: 0.08, delayChildren: 0.1 } } }; const item = { hidden: { opacity: 0, y: 20 }, show: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 300, damping: 24 } } }; // Usage <motion.ul variants={container} initial="hidden" animate="show"> {items.map(i => <motion.li key={i} variants={item}>{i}</motion.li>)} </motion.ul> ``` ### CSS-Only Animations (when Framer Motion is overkill) ```css /* Smooth hover lift */ .card { transition: transform 200ms ease, box-shadow 200ms ease; } .card:hover { transform: translateY(-2px); box-shadow: 0 8px 30px rgba(0,0,0,0.12); } /* Scroll-triggered fade-in */ @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .reveal { animation: fadeInUp 0.6s ease both; animation-timeline: view(); animation-range: entry 0% entry 30%; } ``` --- ## 6. Accessibility (Non-Negotiable) Every interface MUST meet WCAG 2.1 AA minimum: - **Color contrast**: 4.5:1 for text, 3:1 for large text and UI components - **Keyboard navigation**: Every interactive element reachable via Tab. Custom components need `onKeyDown` handlers. - **Focus indicators**: Visible focus rings on ALL interactive elements. Use `focus-visible` (not `focus`). - **Screen readers**: Semantic HTML first (`<nav>`, `<main>`, `<article>`, `<aside>`). ARIA labels where needed. - **Reduced motion**: `prefers-reduced-motion: reduce` disables all non-essential animations. - **Touch targets**: Minimum 44x44px on mobile. ```tsx // Focus ring utility (add to globals.css) @layer base { *:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; border-radius: var(--radius); } } ``` --- ## 7. Performance Rules - **Images**: Use `next/image` with proper `width`/`height` and `priority` for above-fold. WebP/AVIF format. - **Fonts**: Max 2 font families. Use `next/font` with `display: 'swap'` and `preload`. - **Bundle size**: Import shadcn components individually. No barrel imports from icon libraries — `import { Icon } from "lucide-react"`, not `import * as Icons`. - **CSS**: Tailwind purges unused classes automatically. Avoid `@apply` in components (use className directly). - **Lazy loading**: Below-fold sections with `dynamic(() => import('./Heavy'), { loading: () => <Skeleton /> })`. - **Core Web Vitals targets**: LCP < 2.5s, FID < 100ms, CLS < 0.1. --- ## 8. Templates & Quick Starts When a template fits, start from one. Read `references/templates.md` for pre-built structures: 1. **SaaS Landing Page** — Hero + features grid + pricing cards + testimonials + CTA 2. **Dashboard** — Sidebar nav + header + stats cards + data tables + charts 3. **Portfolio** — Hero with name + project grid + about section + contact 4. **Blog/Newsletter** — Article layout + sidebar + newsletter signup 5. **Marketing Site** — Full-bleed sections + alternating layouts + sticky nav 6. **E-commerce Product** — Image gallery + details + reviews + related products 7. **Auth Pages** — Login/signup/forgot-password with social login buttons 8. **Settings Page** — Tabbed navigation + form sections + save/cancel actions 9. **Pricing Page** — Toggle (monthly/annual) + 3-tier cards + FAQ accordion 10. **404/Error Page** — Creative, on-brand error state Each template includes: component structure, responsive breakpoints, animation entry points, and dark mode support. --- ## 9. Review & Audit Mode When reviewing existing UI (instead of building new), follow this checklist: ### Visual Review - [ ] Typography hierarchy is clear (h1 > h2 > h3 > body > caption) - [ ] Color palette is consistent (no ad-hoc hex values) - [ ] Spacing follows a system (4px/8px/16px/24px/32px/48px/64px) - [ ] Dark mode doesn't break (surfaces, borders, text all adjust) - [ ] Responsive at 375px, 768px, 1280px, 1440px ### UX Review - [ ] Primary action is immediately obvious on every page - [ ] Navigation is predictable and consistent - [ ] Loading states exist for all async operations - [ ] Error states are helpful (not just "Something went wrong") - [ ] Empty states guide the user toward action - [ ] Form validation is inline, not just on submit ### Accessibility Review - [ ] Color contrast passes WCAG AA (4.5:1 text, 3:1 UI) - [ ] All images have alt text - [ ] Forms have associated labels - [ ] Focus order is logical - [ ] Screen reader announces page changes (aria-live) - [ ] No content is conveyed by color alone ### Performance Review - [ ] No layout shifts (CLS < 0.1) - [ ] Images are optimized (next/image, WebP) - [ ] Fonts don't cause FOUT/FOIT - [ ] No unnecessary re-renders (check React DevTools) Output findings in format: `file:line — [SEVERITY] description` Severity: 🔴 CRITICAL, 🟡 WARNING, 🔵 SUGGESTION --- ## 10. Anti-Patterns (What NOT to Do) These are the most common mistakes. If you catch yourself doing any of these, stop and fix: - ❌ Using `px` for font sizes (use `rem`) - ❌ Hardcoding colors instead of using CSS variables - ❌ `div` soup (use semantic HTML) - ❌ Forgetting `hover:` and `focus:` states on interactive elements - ❌ Making text unreadable on backgrounds (check contrast!) - ❌ Using `fixed` positioning for navigation without testing scroll behavior - ❌ Ignoring `prefers-reduced-motion` and `prefers-color-scheme` - ❌ Making buttons smaller than 44px touch target on mobile - ❌ Using alerts/confirms instead of proper modal dialogs - ❌ Placeholder text as the only label - ❌ Auto-playing video/audio without user consent - ❌ Layouts that break at 375px (test iPhone SE!) - ❌ Generic "Lorem ipsum" in demos (use real-feeling content) --- ## Design Excellence Reference Consult `references/design-excellence.md` for anti-AI-slop patterns, OKLCH color system, motion rules, interaction states, and improvement modes. --- ## Reference Files Read these when working on specific aspects: - `references/component-patterns.md` — Compound components, composition, slot pattern, polymorphic components, shadcn/ui extension patterns - `references/layout-patterns.md` — Page layouts for landing, dashboard, portfolio, blog, e-commerce with responsive grid systems - `references/templates.md` — 10 ready-to-use page templates with full component structure - `references/color-palettes.md` — 20 curated color palettes organized by mood/industry, including dark mode variants - `references/animation-recipes.md` — Copy-paste animation patterns (scroll reveal, stagger, parallax, magnetic cursor, morphing shapes) - `references/inspiration-patterns.md` — Patterns extracted from curated.design, landing.love, saaspo.com, navbar.gallery, cta.gallery, appmotion.design, component.gallery --- ## Final Checklist (Before Delivering) Every piece of frontend you deliver must pass: - [ ] Bold aesthetic direction — not generic - [ ] Design tokens locked — no ad-hoc values - [ ] shadcn/ui as base — custom layers on top - [ ] Responsive at 4 breakpoints (375, 768, 1280, 1440) - [ ] Dark mode works - [ ] Keyboard navigable - [ ] Focus indicators visible - [ ] Animations respect `prefers-reduced-motion` - [ ] Color contrast passes WCAG AA - [ ] Images optimized with `next/image` - [ ] Fonts loaded via `next/font` - [ ] No anti-patterns from the list above - [ ] Feels intentional — someone would remember this

path: references/animation-recipes.md
content: # Animation Recipes Reference

Copy-paste animation patterns. Each includes Framer Motion and CSS-only versions where applicable.

---

## 1. Page Load Stagger

### Framer Motion

```tsx
"use client";
import { motion } from "framer-motion";

const container = {
  hidden: { opacity: 0 },
  show: { opacity: 1, transition: { staggerChildren: 0.1 } },
};
const item = {
  hidden: { opacity: 0, y: 20 },
  show: { opacity: 1, y: 0, transition: { duration: 0.4, ease: "easeOut" } },
};

export function StaggerList({ children }: { children: React.ReactNode[] }) {
  return (
    <motion.div variants={container} initial="hidden" animate="show" className="space-y-4">
      {children.map((child, i) => (
        <motion.div key={i} variants={item}>{child}</motion.div>
      ))}
    </motion.div>
  );
}
```

### CSS Only

```css
.stagger-item {
  opacity: 0;
  transform: translateY(20px);
  animation: fadeUp 0.4s ease-out forwards;
}
.stagger-item:nth-child(1) { animation-delay: 0ms; }
.stagger-item:nth-child(2) { animation-delay: 100ms; }
.stagger-item:nth-child(3) { animation-delay: 200ms; }
.stagger-item:nth-child(4) { animation-delay: 300ms; }
.stagger-item:nth-child(5) { animation-delay: 400ms; }

@keyframes fadeUp {
  to { opacity: 1; transform: translateY(0); }
}
```

---

## 2. Scroll-Triggered Reveal

### Framer Motion (with whileInView)

```tsx
<motion.div
  initial={{ opacity: 0, y: 40 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: "-100px" }}
  transition={{ duration: 0.5, ease: "easeOut" }}
>
  {children}
</motion.div>
```

### CSS + Intersection Observer

```tsx
"use client";
import { useEffect, useRef, useState } from "react";

export function ScrollReveal({ children, className }: { children: React.ReactNode; className?: string }) {
  const ref = useRef<HTMLDivElement>(null);
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => { if (entry.isIntersecting) setVisible(true); },
      { rootMargin: "-100px" }
    );
    if (ref.current) observer.observe(ref.current);
    return () => observer.disconnect();
  }, []);

  return (
    <div
      ref={ref}
      className={`transition-all duration-500 ease-out ${visible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-10"} ${className}`}
    >
      {children}
    </div>
  );
}
```

---

## 3. Parallax Section

### CSS Scroll

```tsx
<section className="relative h-[60vh] overflow-hidden">
  <div
    className="absolute inset-0 bg-cover bg-center bg-fixed"
    style={{ backgroundImage: "url(/hero.jpg)" }}
  />
  <div className="relative z-10 flex h-full items-center justify-center bg-black/40">
    <h2 className="text-4xl font-bold text-white">Parallax Title</h2>
  </div>
</section>
```

### Framer Motion (scroll-driven)

```tsx
"use client";
import { motion, useScroll, useTransform } from "framer-motion";
import { useRef } from "react";

export function Parallax({ children }: { children: React.ReactNode }) {
  const ref = useRef(null);
  const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "end start"] });
  const y = useTransform(scrollYProgress, [0, 1], ["-20%", "20%"]);

  return (
    <div ref={ref} className="relative h-[60vh] overflow-hidden">
      <motion.div style={{ y }} className="absolute inset-[-20%] bg-cover bg-center" />
      <div className="relative z-10 flex h-full items-center justify-center">{children}</div>
    </div>
  );
}
```

---

## 4. Magnetic Cursor Effect

```tsx
"use client";
import { motion, useMotionValue, useSpring } from "framer-motion";
import { useRef } from "react";

export function MagneticButton({ children }: { children: React.ReactNode }) {
  const ref = useRef<HTMLButtonElement>(null);
  const x = useMotionValue(0);
  const y = useMotionValue(0);
  const springX = useSpring(x, { stiffness: 300, damping: 20 });
  const springY = useSpring(y, { stiffness: 300, damping: 20 });

  const handleMouse = (e: React.MouseEvent) => {
    const rect = ref.current!.getBoundingClientRect();
    x.set((e.clientX - rect.left - rect.width / 2) * 0.3);
    y.set((e.clientY - rect.top - rect.height / 2) * 0.3);
  };
  const reset = () => { x.set(0); y.set(0); };

  return (
    <motion.button
      ref={ref}
      style={{ x: springX, y: springY }}
      onMouseMove={handleMouse}
      onMouseLeave={reset}
      className="rounded-lg bg-primary px-6 py-3 text-primary-foreground font-medium"
    >
      {children}
    </motion.button>
  );
}
```

---

## 5. Morphing Shape (SVG)

```tsx
"use client";
import { motion } from "framer-motion";

const paths = [
  "M 0,100 C 50,0 100,200 200,100 C 300,0 350,200 400,100 L 400,300 L 0,300 Z",
  "M 0,150 C 80,80 150,250 250,120 C 320,50 370,220 400,150 L 400,300 L 0,300 Z",
];

export function MorphBlob() {
  return (
    <svg viewBox="0 0 400 300" className="w-full h-auto fill-primary/20">
      <motion.path
        d={paths[0]}
        animate={{ d: paths }}
        transition={{ duration: 4, repeat: Infinity, repeatType: "reverse", ease: "easeInOut" }}
      />
    </svg>
  );
}
```

---

## 6. Number Count-Up

```tsx
"use client";
import { useEffect, useRef, useState } from "react";

export function CountUp({ target, duration = 2000 }: { target: number; duration?: number }) {
  const [count, setCount] = useState(0);
  const ref = useRef<HTMLSpanElement>(null);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      if (!entry.isIntersecting) return;
      let start = 0;
      const step = (ts: number) => {
        if (!start) start = ts;
        const progress = Math.min((ts - start) / duration, 1);
        setCount(Math.floor(progress * target));
        if (progress < 1) requestAnimationFrame(step);
      };
      requestAnimationFrame(step);
      observer.disconnect();
    }, { threshold: 0.5 });
    if (ref.current) observer.observe(ref.current);
    return () => observer.disconnect();
  }, [target, duration]);

  return <span ref={ref} className="tabular-nums">{count.toLocaleString()}</span>;
}
```

---

## 7. Typewriter Effect

```tsx
"use client";
import { useState, useEffect } from "react";

export function Typewriter({ words, speed = 80, pause = 2000 }: { words: string[]; speed?: number; pause?: number }) {
  const [text, setText] = useState("");
  const [wordIdx, setWordIdx] = useState(0);
  const [deleting, setDeleting] = useState(false);

  useEffect(() => {
    const word = words[wordIdx];
    const timeout = setTimeout(() => {
      if (!deleting) {
        setText(word.slice(0, text.length + 1));
        if (text.length + 1 === word.length) setTimeout(() => setDeleting(true), pause);
      } else {
        setText(word.slice(0, text.length - 1));
        if (text.length === 0) { setDeleting(false); setWordIdx((i) => (i + 1) % words.length); }
      }
    }, deleting ? speed / 2 : speed);
    return () => clearTimeout(timeout);
  }, [text, deleting, wordIdx, words, speed, pause]);

  return <span>{text}<span className="animate-pulse">|</span></span>;
}
```

---

## 8. Hover Card Lift with Shadow

### CSS (Tailwind)

```html
<div class="rounded-xl border bg-card p-6 transition-all duration-300
            hover:-translate-y-1 hover:shadow-lg hover:shadow-primary/5">
  Card content
</div>
```

### Framer Motion

```tsx
<motion.div
  whileHover={{ y: -4, boxShadow: "0 20px 40px rgba(0,0,0,0.1)" }}
  transition={{ type: "spring", stiffness: 300, damping: 20 }}
  className="rounded-xl border bg-card p-6"
>
  Card content
</motion.div>
```

---

## 9. Accordion Open/Close with Spring

```tsx
"use client";
import { motion, AnimatePresence } from "framer-motion";

export function AnimatedAccordion({ isOpen, children }: { isOpen: boolean; children: React.ReactNode }) {
  return (
    <AnimatePresence initial={false}>
      {isOpen && (
        <motion.div
          initial={{ height: 0, opacity: 0 }}
          animate={{ height: "auto", opacity: 1 }}
          exit={{ height: 0, opacity: 0 }}
          transition={{ type: "spring", stiffness: 300, damping: 30 }}
          className="overflow-hidden"
        >
          {children}
        </motion.div>
      )}
    </AnimatePresence>
  );
}
```

---

## 10. Tab Switching with Layout Animation

```tsx
"use client";
import { useState } from "react";
import { motion } from "framer-motion";

const tabs = ["Overview", "Features", "Pricing"];

export function AnimatedTabs() {
  const [active, setActive] = useState(tabs[0]);
  return (
    <div className="flex gap-1 rounded-lg bg-muted p-1">
      {tabs.map((tab) => (
        <button key={tab} onClick={() => setActive(tab)} className="relative rounded-md px-4 py-2 text-sm font-medium">
          {active === tab && (
            <motion.div
              layoutId="active-tab"
              className="absolute inset-0 rounded-md bg-background shadow-sm"
              transition={{ type: "spring", stiffness: 400, damping: 30 }}
            />
          )}
          <span className="relative z-10">{tab}</span>
        </button>
      ))}
    </div>
  );
}
```

---

## 11. Image Reveal on Scroll

```tsx
<motion.div
  initial={{ clipPath: "inset(100% 0 0 0)" }}
  whileInView={{ clipPath: "inset(0% 0 0 0)" }}
  viewport={{ once: true }}
  transition={{ duration: 0.8, ease: [0.25, 1, 0.5, 1] }}
>
  <img src="/image.jpg" alt="" className="w-full rounded-xl" />
</motion.div>
```

### CSS Only

```css
.reveal-image {
  clip-path: inset(100% 0 0 0);
  animation: revealUp 0.8s cubic-bezier(0.25, 1, 0.5, 1) forwards;
}
@keyframes revealUp {
  to { clip-path: inset(0% 0 0 0); }
}
```

---

## 12. Navbar Scroll Hide/Show

```tsx
"use client";
import { useState, useEffect } from "react";
import { cn } from "@/lib/utils";

export function ScrollNavbar({ children }: { children: React.ReactNode }) {
  const [hidden, setHidden] = useState(false);
  const [lastY, setLastY] = useState(0);

  useEffect(() => {
    const handleScroll = () => {
      const y = window.scrollY;
      setHidden(y > lastY && y > 80);
      setLastY(y);
    };
    window.addEventListener("scroll", handleScroll, { passive: true });
    return () => window.removeEventListener("scroll", handleScroll);
  }, [lastY]);

  return (
    <header className={cn(
      "sticky top-0 z-50 border-b bg-background/80 backdrop-blur-sm transition-transform duration-300",
      hidden ? "-translate-y-full" : "translate-y-0"
    )}>
      {children}
    </header>
  );
}
```

---

## 13. Skeleton Loading Shimmer

### CSS Only (Tailwind)

```html
<div class="space-y-4 animate-pulse">
  <div class="h-4 w-3/4 rounded bg-muted"></div>
  <div class="h-4 w-1/2 rounded bg-muted"></div>
  <div class="h-32 rounded-xl bg-muted"></div>
</div>
```

### Custom shimmer gradient

```css
.skeleton {
  background: linear-gradient(90deg, hsl(var(--muted)) 25%, hsl(var(--muted)/0.5) 50%, hsl(var(--muted)) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
```

---

## 14. Toast / Notification Slide-In

### Framer Motion

```tsx
<motion.div
  initial={{ x: "100%", opacity: 0 }}
  animate={{ x: 0, opacity: 1 }}
  exit={{ x: "100%", opacity: 0 }}
  transition={{ type: "spring", stiffness: 400, damping: 30 }}
  className="fixed bottom-4 right-4 z-50 rounded-lg border bg-card p-4 shadow-lg"
>
  <p className="text-sm font-medium">Changes saved successfully.</p>
</motion.div>
```

### CSS Only

```css
.toast-enter {
  animation: slideInRight 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.toast-exit {
  animation: slideOutRight 0.2s ease-in forwards;
}
@keyframes slideInRight {
  from { transform: translateX(100%); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOutRight {
  to { transform: translateX(100%); opacity: 0; }
}
```

---

## 15. Modal Backdrop Blur

### Framer Motion

```tsx
<AnimatePresence>
  {isOpen && (
    <>
      <motion.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        className="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
        onClick={onClose}
      />
      <motion.div
        initial={{ opacity: 0, scale: 0.95, y: 10 }}
        animate={{ opacity: 1, scale: 1, y: 0 }}
        exit={{ opacity: 0, scale: 0.95, y: 10 }}
        transition={{ type: "spring", stiffness: 400, damping: 30 }}
        className="fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2 rounded-2xl border bg-card p-6 shadow-xl w-full max-w-md"
      >
        {children}
      </motion.div>
    </>
  )}
</AnimatePresence>
```

### CSS Only

```css
.backdrop {
  position: fixed; inset: 0; z-index: 50;
  background: rgba(0,0,0,0.5); backdrop-filter: blur(4px);
  animation: fadeIn 0.2s ease-out;
}
.modal {
  animation: modalIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes modalIn {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }
  to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
```

---

## Easing Reference

| Name | CSS | Framer Motion | Use Case |
|------|-----|--------------|----------|
| Ease out | `cubic-bezier(0, 0, 0.2, 1)` | `"easeOut"` | Entrances |
| Ease in-out | `cubic-bezier(0.4, 0, 0.2, 1)` | `"easeInOut"` | Transitions |
| Spring | N/A | `{ type: "spring", stiffness: 300, damping: 30 }` | Interactive |
| Sharp | `cubic-bezier(0.16, 1, 0.3, 1)` | `[0.16, 1, 0.3, 1]` | Modals, popups |
| Bounce | N/A | `{ type: "spring", stiffness: 400, damping: 15 }` | Playful |


path: references/color-palettes.md
content: # Color Palettes Reference

20 curated palettes organized by mood/industry. Each includes 6 colors for both light and dark modes with Tailwind config snippets.

---

## SaaS / Tech

### 1. Midnight Indigo

Clean, trustworthy, modern SaaS feel.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#4F46E5` | `#818CF8` |
| Accent | `#06B6D4` | `#22D3EE` |
| Surface | `#FFFFFF` | `#0F172A` |
| Text | `#1E293B` | `#F1F5F9` |
| Border | `#E2E8F0` | `#334155` |
| Muted | `#F8FAFC` | `#1E293B` |

```js
// tailwind.config.js
colors: {
  brand: { DEFAULT: "#4F46E5", dark: "#818CF8" },
  accent: { DEFAULT: "#06B6D4", dark: "#22D3EE" },
  surface: { DEFAULT: "#FFFFFF", dark: "#0F172A" },
}
```

### 2. Electric Violet

Bold, developer-focused, modern.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#7C3AED` | `#A78BFA` |
| Accent | `#F59E0B` | `#FBBF24` |
| Surface | `#FAFAFA` | `#09090B` |
| Text | `#18181B` | `#FAFAFA` |
| Border | `#E4E4E7` | `#27272A` |
| Muted | `#F4F4F5` | `#18181B` |

```js
colors: {
  brand: { DEFAULT: "#7C3AED", dark: "#A78BFA" },
  accent: { DEFAULT: "#F59E0B", dark: "#FBBF24" },
}
```

### 3. Ocean Blue

Enterprise, reliable, professional.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#2563EB` | `#60A5FA` |
| Accent | `#10B981` | `#34D399` |
| Surface | `#FFFFFF` | `#0C1222` |
| Text | `#1E293B` | `#E2E8F0` |
| Border | `#CBD5E1` | `#334155` |
| Muted | `#F1F5F9` | `#1E293B` |

```js
colors: {
  brand: { DEFAULT: "#2563EB", dark: "#60A5FA" },
  accent: { DEFAULT: "#10B981", dark: "#34D399" },
}
```

### 4. Slate Minimal

Ultra-clean, Stripe-inspired.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#0F172A` | `#F8FAFC` |
| Accent | `#6366F1` | `#818CF8` |
| Surface | `#FFFFFF` | `#020617` |
| Text | `#334155` | `#CBD5E1` |
| Border | `#E2E8F0` | `#1E293B` |
| Muted | `#F8FAFC` | `#0F172A` |

```js
colors: {
  brand: { DEFAULT: "#0F172A", dark: "#F8FAFC" },
  accent: { DEFAULT: "#6366F1", dark: "#818CF8" },
}
```

---

## Health / Wellness

### 5. Sage Green

Calm, organic, wellness-focused.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#059669` | `#6EE7B7` |
| Accent | `#D97706` | `#FCD34D` |
| Surface | `#FFFBF5` | `#0D1B12` |
| Text | `#1C3829` | `#ECFDF5` |
| Border | `#D1E7DD` | `#1F3D2B` |
| Muted | `#F0FDF4` | `#14291C` |

```js
colors: {
  brand: { DEFAULT: "#059669", dark: "#6EE7B7" },
  accent: { DEFAULT: "#D97706", dark: "#FCD34D" },
  surface: { DEFAULT: "#FFFBF5", dark: "#0D1B12" },
}
```

### 6. Lavender Bloom

Soft, therapeutic, mindful.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#8B5CF6` | `#C4B5FD` |
| Accent | `#EC4899` | `#F9A8D4` |
| Surface | `#FEFCFF` | `#110D1B` |
| Text | `#2E1065` | `#EDE9FE` |
| Border | `#DDD6FE` | `#3B2770` |
| Muted | `#F5F3FF` | `#1E1533` |

```js
colors: {
  brand: { DEFAULT: "#8B5CF6", dark: "#C4B5FD" },
  accent: { DEFAULT: "#EC4899", dark: "#F9A8D4" },
}
```

### 7. Ocean Breeze

Fresh, aquatic, spa-inspired.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#0891B2` | `#67E8F9` |
| Accent | `#059669` | `#6EE7B7` |
| Surface | `#F0FDFA` | `#042F2E` |
| Text | `#134E4A` | `#CCFBF1` |
| Border | `#99F6E4` | `#115E59` |
| Muted | `#ECFDF5` | `#064E3B` |

```js
colors: {
  brand: { DEFAULT: "#0891B2", dark: "#67E8F9" },
  accent: { DEFAULT: "#059669", dark: "#6EE7B7" },
}
```

### 8. Warm Earth

Grounded, natural, holistic.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#B45309` | `#FCD34D` |
| Accent | `#65A30D` | `#A3E635` |
| Surface | `#FFFBEB` | `#1C1208` |
| Text | `#451A03` | `#FEF3C7` |
| Border | `#FDE68A` | `#713F12` |
| Muted | `#FEF3C7` | `#3B2408` |

```js
colors: {
  brand: { DEFAULT: "#B45309", dark: "#FCD34D" },
  accent: { DEFAULT: "#65A30D", dark: "#A3E635" },
}
```

---

## Luxury / Premium

### 9. Black Gold

High-end, exclusive, editorial.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#1C1917` | `#FAFAF9` |
| Accent | `#CA8A04` | `#FACC15` |
| Surface | `#FAFAF9` | `#0C0A09` |
| Text | `#292524` | `#E7E5E4` |
| Border | `#D6D3D1` | `#292524` |
| Muted | `#F5F5F4` | `#1C1917` |

```js
colors: {
  brand: { DEFAULT: "#1C1917", dark: "#FAFAF9" },
  accent: { DEFAULT: "#CA8A04", dark: "#FACC15" },
}
```

### 10. Royal Navy

Sophisticated, banking, finance.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#1E3A5F` | `#93C5FD` |
| Accent | `#B45309` | `#FCD34D` |
| Surface | `#FAFBFD` | `#0A1628` |
| Text | `#0F172A` | `#E2E8F0` |
| Border | `#CBD5E1` | `#1E3A5F` |
| Muted | `#EFF6FF` | `#0F1D32` |

```js
colors: {
  brand: { DEFAULT: "#1E3A5F", dark: "#93C5FD" },
  accent: { DEFAULT: "#B45309", dark: "#FCD34D" },
}
```

### 11. Rosewood

Elegant, fashion, beauty.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#9F1239` | `#FDA4AF` |
| Accent | `#A16207` | `#FDE047` |
| Surface | `#FFF1F2` | `#1A0A0E` |
| Text | `#4C0519` | `#FFE4E6` |
| Border | `#FECDD3` | `#4C0519` |
| Muted | `#FFF1F2` | `#2A0E15` |

```js
colors: {
  brand: { DEFAULT: "#9F1239", dark: "#FDA4AF" },
  accent: { DEFAULT: "#A16207", dark: "#FDE047" },
}
```

### 12. Champagne

Refined, hospitality, events.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#78716C` | `#D6D3D1` |
| Accent | `#B45309` | `#FCD34D` |
| Surface | `#FAFAF9` | `#0C0A09` |
| Text | `#292524` | `#E7E5E4` |
| Border | `#D6D3D1` | `#44403C` |
| Muted | `#F5F5F4` | `#1C1917` |

```js
colors: {
  brand: { DEFAULT: "#78716C", dark: "#D6D3D1" },
  accent: { DEFAULT: "#B45309", dark: "#FCD34D" },
}
```

---

## Creative / Bold

### 13. Neon Pop

Vibrant, youthful, gaming.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#DC2626` | `#FCA5A5` |
| Accent | `#2563EB` | `#93C5FD` |
| Surface | `#FFFFFF` | `#09090B` |
| Text | `#18181B` | `#FAFAFA` |
| Border | `#E4E4E7` | `#27272A` |
| Muted | `#F4F4F5` | `#18181B` |

```js
colors: {
  brand: { DEFAULT: "#DC2626", dark: "#FCA5A5" },
  accent: { DEFAULT: "#2563EB", dark: "#93C5FD" },
}
```

### 14. Sunset Gradient

Warm, creative agency, portfolio.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#EA580C` | `#FB923C` |
| Accent | `#DB2777` | `#F472B6` |
| Surface | `#FFFBEB` | `#120B05` |
| Text | `#431407` | `#FED7AA` |
| Border | `#FDBA74` | `#7C2D12` |
| Muted | `#FFF7ED` | `#1C0F05` |

**Gradient:** `bg-gradient-to-r from-[#EA580C] to-[#DB2777]`

```js
colors: {
  brand: { DEFAULT: "#EA580C", dark: "#FB923C" },
  accent: { DEFAULT: "#DB2777", dark: "#F472B6" },
}
```

### 15. Electric Lime

High-energy, startup, disruptive.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#65A30D` | `#A3E635` |
| Accent | `#7C3AED` | `#A78BFA` |
| Surface | `#FAFFF5` | `#0A1205` |
| Text | `#1A2E05` | `#ECFCCB` |
| Border | `#BEF264` | `#365314` |
| Muted | `#F7FEE7` | `#14200A` |

```js
colors: {
  brand: { DEFAULT: "#65A30D", dark: "#A3E635" },
  accent: { DEFAULT: "#7C3AED", dark: "#A78BFA" },
}
```

### 16. Coral Punch

Playful, social, community.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#F43F5E` | `#FB7185` |
| Accent | `#0EA5E9` | `#38BDF8` |
| Surface | `#FFFFFF` | `#0F0709` |
| Text | `#1C1917` | `#FFF1F2` |
| Border | `#FECDD3` | `#4C0519` |
| Muted | `#FFF1F2` | `#1A0A0E` |

```js
colors: {
  brand: { DEFAULT: "#F43F5E", dark: "#FB7185" },
  accent: { DEFAULT: "#0EA5E9", dark: "#38BDF8" },
}
```

---

## Dark Mode First

### 17. Obsidian

GitHub-inspired developer dark.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#F8FAFC` | `#F8FAFC` |
| Accent | `#3B82F6` | `#60A5FA` |
| Surface | `#FFFFFF` | `#0D1117` |
| Text | `#1F2937` | `#C9D1D9` |
| Border | `#D1D5DB` | `#30363D` |
| Muted | `#F3F4F6` | `#161B22` |

```js
colors: {
  brand: { DEFAULT: "#F8FAFC", dark: "#F8FAFC" },
  accent: { DEFAULT: "#3B82F6", dark: "#60A5FA" },
  surface: { DEFAULT: "#FFFFFF", dark: "#0D1117" },
}
```

### 18. Deep Space

Ultra-dark, astronomy, data viz.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#6366F1` | `#818CF8` |
| Accent | `#06B6D4` | `#22D3EE` |
| Surface | `#F8FAFC` | `#030712` |
| Text | `#1E293B` | `#E2E8F0` |
| Border | `#E2E8F0` | `#1E293B` |
| Muted | `#F1F5F9` | `#0F172A` |

```js
colors: {
  brand: { DEFAULT: "#6366F1", dark: "#818CF8" },
  accent: { DEFAULT: "#06B6D4", dark: "#22D3EE" },
  surface: { DEFAULT: "#F8FAFC", dark: "#030712" },
}
```

### 19. Charcoal Ember

Warm dark, comfortable reading.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#F97316` | `#FB923C` |
| Accent | `#EAB308` | `#FACC15` |
| Surface | `#FAFAF9` | `#1A1614` |
| Text | `#292524` | `#D6D3D1` |
| Border | `#D6D3D1` | `#44403C` |
| Muted | `#F5F5F4` | `#292524` |

```js
colors: {
  brand: { DEFAULT: "#F97316", dark: "#FB923C" },
  accent: { DEFAULT: "#EAB308", dark: "#FACC15" },
  surface: { DEFAULT: "#FAFAF9", dark: "#1A1614" },
}
```

### 20. Phosphor Green

Terminal, hacker, retro-tech.

| Role | Light Mode | Dark Mode |
|------|-----------|-----------|
| Brand | `#22C55E` | `#4ADE80` |
| Accent | `#06B6D4` | `#22D3EE` |
| Surface | `#F0FDF4` | `#020A04` |
| Text | `#14532D` | `#BBF7D0` |
| Border | `#86EFAC` | `#14532D` |
| Muted | `#DCFCE7` | `#052E16` |

```js
colors: {
  brand: { DEFAULT: "#22C55E", dark: "#4ADE80" },
  accent: { DEFAULT: "#06B6D4", dark: "#22D3EE" },
  surface: { DEFAULT: "#F0FDF4", dark: "#020A04" },
}
```

---

## Using a Palette in Tailwind

Full integration with CSS variables for theme switching:

```css
/* globals.css */
@layer base {
  :root {
    --brand: 239 84% 67%;      /* #4F46E5 in HSL */
    --accent: 188 94% 43%;     /* #06B6D4 */
    --surface: 0 0% 100%;
    --foreground: 215 28% 17%;
    --border: 214 32% 91%;
    --muted: 210 40% 98%;
  }
  .dark {
    --brand: 232 92% 76%;      /* #818CF8 */
    --accent: 188 78% 56%;     /* #22D3EE */
    --surface: 217 33% 11%;
    --foreground: 210 40% 96%;
    --border: 217 19% 27%;
    --muted: 215 28% 17%;
  }
}
```

```js
// tailwind.config.js
theme: {
  extend: {
    colors: {
      brand: "hsl(var(--brand) / <alpha-value>)",
      accent: "hsl(var(--accent) / <alpha-value>)",
      surface: "hsl(var(--surface) / <alpha-value>)",
    }
  }
}
```


path: references/component-patterns.md
content: # Component Patterns Reference

Copy-paste patterns for Next.js + Tailwind CSS + shadcn/ui projects.

---

## 1. Compound Components

Use React Context to share state between a parent and its children without prop drilling.

### Accordion Example

```tsx
// components/accordion.tsx
"use client";
import { createContext, useContext, useState, ReactNode } from "react";
import { cn } from "@/lib/utils";
import { ChevronDown } from "lucide-react";

type AccordionCtx = {
  openItem: string | null;
  toggle: (id: string) => void;
};

const AccordionContext = createContext<AccordionCtx | null>(null);
const useAccordion = () => {
  const ctx = useContext(AccordionContext);
  if (!ctx) throw new Error("AccordionItem must be inside Accordion");
  return ctx;
};

export function Accordion({ children, className }: { children: ReactNode; className?: string }) {
  const [openItem, setOpenItem] = useState<string | null>(null);
  const toggle = (id: string) => setOpenItem((prev) => (prev === id ? null : id));

  return (
    <AccordionContext.Provider value={{ openItem, toggle }}>
      <div className={cn("divide-y divide-border rounded-lg border", className)}>
        {children}
      </div>
    </AccordionContext.Provider>
  );
}

export function AccordionItem({ id, trigger, children }: {
  id: string; trigger: string; children: ReactNode;
}) {
  const { openItem, toggle } = useAccordion();
  const isOpen = openItem === id;

  return (
    <div>
      <button
        onClick={() => toggle(id)}
        className="flex w-full items-center justify-between px-4 py-3 text-sm font-medium hover:bg-muted/50 transition-colors"
      >
        {trigger}
        <ChevronDown className={cn("h-4 w-4 transition-transform", isOpen && "rotate-180")} />
      </button>
      <div className={cn(
        "grid transition-all duration-200",
        isOpen ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0"
      )}>
        <div className="overflow-hidden px-4 pb-3 text-sm text-muted-foreground">
          {children}
        </div>
      </div>
    </div>
  );
}
```

### Menu/MenuItem Example

```tsx
"use client";
import { createContext, useContext, useState, ReactNode } from "react";
import { cn } from "@/lib/utils";

type MenuCtx = { activeItem: string | null; setActive: (id: string) => void };
const MenuContext = createContext<MenuCtx | null>(null);

export function Menu({ children, className }: { children: ReactNode; className?: string }) {
  const [activeItem, setActive] = useState<string | null>(null);
  return (
    <MenuContext.Provider value={{ activeItem, setActive }}>
      <nav className={cn("flex flex-col gap-1", className)}>{children}</nav>
    </MenuContext.Provider>
  );
}

export function MenuItem({ id, children }: { id: string; children: ReactNode }) {
  const ctx = useContext(MenuContext)!;
  return (
    <button
      onClick={() => ctx.setActive(id)}
      className={cn(
        "rounded-md px-3 py-2 text-sm text-left transition-colors",
        ctx.activeItem === id
          ? "bg-primary text-primary-foreground"
          : "hover:bg-muted text-muted-foreground"
      )}
    >
      {children}
    </button>
  );
}
```

---

## 2. Polymorphic Components

Render as different HTML elements or components using the `as` prop.

```tsx
import { ElementType, ComponentPropsWithoutRef, ReactNode } from "react";
import { cn } from "@/lib/utils";

type BoxProps<T extends ElementType> = {
  as?: T;
  children: ReactNode;
  className?: string;
} & ComponentPropsWithoutRef<T>;

export function Box<T extends ElementType = "div">({
  as,
  children,
  className,
  ...props
}: BoxProps<T>) {
  const Component = as || "div";
  return (
    <Component className={cn(className)} {...props}>
      {children}
    </Component>
  );
}

// Usage:
// <Box as="section" className="p-4">Section content</Box>
// <Box as="a" href="/about" className="text-blue-600 underline">Link</Box>
// <Box as={Link} href="/about">Next.js Link</Box>
```

---

## 3. Composition Over Configuration

### Slot Pattern

```tsx
import { ReactNode } from "react";

type CardProps = {
  header?: ReactNode;
  footer?: ReactNode;
  children: ReactNode;
};

export function Card({ header, footer, children }: CardProps) {
  return (
    <div className="rounded-xl border bg-card shadow-sm">
      {header && <div className="border-b px-6 py-4">{header}</div>}
      <div className="px-6 py-4">{children}</div>
      {footer && <div className="border-t bg-muted/50 px-6 py-3">{footer}</div>}
    </div>
  );
}

// Usage:
// <Card
//   header={<h3 className="font-semibold">Title</h3>}
//   footer={<Button>Save</Button>}
// >
//   <p>Content goes here</p>
// </Card>
```

### Render Props

```tsx
type ListProps<T> = {
  items: T[];
  renderItem: (item: T, index: number) => ReactNode;
  emptyState?: ReactNode;
  className?: string;
};

export function List<T>({ items, renderItem, emptyState, className }: ListProps<T>) {
  if (items.length === 0) return <>{emptyState || <p className="text-muted-foreground text-sm">No items</p>}</>;
  return <ul className={cn("space-y-2", className)}>{items.map((item, i) => <li key={i}>{renderItem(item, i)}</li>)}</ul>;
}
```

---

## 4. Extending shadcn/ui with CVA Variants

```tsx
// components/ui/badge.tsx (extended)
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const badgeVariants = cva(
  "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground",
        secondary: "bg-secondary text-secondary-foreground",
        destructive: "bg-destructive text-destructive-foreground",
        outline: "border border-input bg-background text-foreground",
        success: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-400",
        warning: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400",
        info: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400",
      },
      size: {
        sm: "px-2 py-0.5 text-[10px]",
        md: "px-2.5 py-0.5 text-xs",
        lg: "px-3 py-1 text-sm",
      },
    },
    defaultVariants: { variant: "default", size: "md" },
  }
);

type BadgeProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof badgeVariants>;

export function Badge({ className, variant, size, ...props }: BadgeProps) {
  return <div className={cn(badgeVariants({ variant, size }), className)} {...props} />;
}
```

---

## 5. Form Pattern: react-hook-form + Zod + shadcn

```tsx
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";

const schema = z.object({
  name: z.string().min(2, "Name must be at least 2 characters"),
  email: z.string().email("Invalid email address"),
  role: z.enum(["admin", "editor", "viewer"], { required_error: "Select a role" }),
});

type FormValues = z.infer<typeof schema>;

export function UserForm({ onSubmit }: { onSubmit: (data: FormValues) => void }) {
  const form = useForm<FormValues>({
    resolver: zodResolver(schema),
    defaultValues: { name: "", email: "", role: undefined },
  });

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField control={form.control} name="name" render={({ field }) => (
          <FormItem>
            <FormLabel>Name</FormLabel>
            <FormControl><Input placeholder="Jane Doe" {...field} /></FormControl>
            <FormMessage />
          </FormItem>
        )} />
        <FormField control={form.control} name="email" render={({ field }) => (
          <FormItem>
            <FormLabel>Email</FormLabel>
            <FormControl><Input type="email" placeholder="[REDACTED_SECRET]" {...field} /></FormControl>
            <FormMessage />
          </FormItem>
        )} />
        <FormField control={form.control} name="role" render={({ field }) => (
          <FormItem>
            <FormLabel>Role</FormLabel>
            <Select onValueChange={field.onChange} defaultValue={field.value}>
              <FormControl><SelectTrigger><SelectValue placeholder="Select role" /></SelectTrigger></FormControl>
              <SelectContent>
                <SelectItem value="admin">Admin</SelectItem>
                <SelectItem value="editor">Editor</SelectItem>
                <SelectItem value="viewer">Viewer</SelectItem>
              </SelectContent>
            </Select>
            <FormMessage />
          </FormItem>
        )} />
        <Button type="submit" className="w-full">Create User</Button>
      </form>
    </Form>
  );
}
```

---

## 6. Data Table: TanStack Table + shadcn

```tsx
"use client";
import { useState } from "react";
import {
  ColumnDef, flexRender, getCoreRowModel, getSortedRowModel,
  getPaginationRowModel, getFilteredRowModel, useReactTable, SortingState,
} from "@tanstack/react-table";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ArrowUpDown } from "lucide-react";

type User = { id: string; name: string; email: string; status: "active" | "inactive" };

const columns: ColumnDef<User>[] = [
  {
    accessorKey: "name",
    header: ({ column }) => (
      <Button variant="ghost" onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}>
        Name <ArrowUpDown className="ml-2 h-4 w-4" />
      </Button>
    ),
  },
  { accessorKey: "email", header: "Email" },
  {
    accessorKey: "status",
    header: "Status",
    cell: ({ row }) => (
      <span className={row.getValue("status") === "active"
        ? "text-emerald-600 font-medium" : "text-muted-foreground"}>
        {row.getValue("status")}
      </span>
    ),
  },
];

export function UsersTable({ data }: { data: User[] }) {
  const [sorting, setSorting] = useState<SortingState>([]);
  const [globalFilter, setGlobalFilter] = useState("");

  const table = useReactTable({
    data, columns,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    onSortingChange: setSorting,
    onGlobalFilterChange: setGlobalFilter,
    state: { sorting, globalFilter },
  });

  return (
    <div className="space-y-4">
      <Input placeholder="Search..." value={globalFilter} onChange={(e) => setGlobalFilter(e.target.value)} className="max-w-sm" />
      <div className="rounded-md border">
        <Table>
          <TableHeader>
            {table.getHeaderGroups().map((hg) => (
              <TableRow key={hg.id}>
                {hg.headers.map((h) => (
                  <TableHead key={h.id}>{h.isPlaceholder ? null : flexRender(h.column.columnDef.header, h.getContext())}</TableHead>
                ))}
              </TableRow>
            ))}
          </TableHeader>
          <TableBody>
            {table.getRowModel().rows.length ? (
              table.getRowModel().rows.map((row) => (
                <TableRow key={row.id}>
                  {row.getVisibleCells().map((cell) => (
                    <TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
                  ))}
                </TableRow>
              ))
            ) : (
              <TableRow><TableCell colSpan={columns.length} className="h-24 text-center">No results.</TableCell></TableRow>
            )}
          </TableBody>
        </Table>
      </div>
      <div className="flex items-center justify-end gap-2">
        <Button variant="outline" size="sm" onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>Previous</Button>
        <Button variant="outline" size="sm" onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>Next</Button>
      </div>
    </div>
  );
}
```

---

## 7. Command Palette: cmdk + shadcn Command

```tsx
"use client";
import { useEffect, useState } from "react";
import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from "@/components/ui/command";
import { useRouter } from "next/navigation";
import { Settings, User, FileText, Search, Home, LogOut } from "lucide-react";

const navItems = [
  { label: "Home", icon: Home, href: "/" },
  { label: "Documents", icon: FileText, href: "/docs" },
  { label: "Settings", icon: Settings, href: "/settings" },
  { label: "Profile", icon: User, href: "/profile" },
];

const actions = [
  { label: "Search docs...", icon: Search, action: "search" },
  { label: "Sign out", icon: LogOut, action: "signout" },
];

export function CommandPalette() {
  const [open, setOpen] = useState(false);
  const router = useRouter();

  useEffect(() => {
    const down = (e: KeyboardEvent) => {
      if (e.key === "k" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setOpen((o) => !o); }
    };
    document.addEventListener("keydown", down);
    return () => document.removeEventListener("keydown", down);
  }, []);

  const runAction = (action: string) => {
    setOpen(false);
    if (action === "signout") { /* sign out logic */ }
  };

  return (
    <CommandDialog open={open} onOpenChange={setOpen}>
      <CommandInput placeholder="Type a command or search..." />
      <CommandList>
        <CommandEmpty>No results found.</CommandEmpty>
        <CommandGroup heading="Navigation">
          {navItems.map((item) => (
            <CommandItem key={item.href} onSelect={() => { setOpen(false); router.push(item.href); }}>
              <item.icon className="mr-2 h-4 w-4" /> {item.label}
            </CommandItem>
          ))}
        </CommandGroup>
        <CommandSeparator />
        <CommandGroup heading="Actions">
          {actions.map((item) => (
            <CommandItem key={item.action} onSelect={() => runAction(item.action)}>
              <item.icon className="mr-2 h-4 w-4" /> {item.label}
            </CommandItem>
          ))}
        </CommandGroup>
      </CommandList>
    </CommandDialog>
  );
}

// Trigger button for non-keyboard users:
// <Button variant="outline" className="text-muted-foreground" onClick={() => setOpen(true)}>
//   <Search className="mr-2 h-4 w-4" /> Search... <kbd className="ml-auto text-xs bg-muted px-1.5 py-0.5 rounded">Cmd+K</kbd>
// </Button>
```


path: references/design-excellence.md
content: # Design Excellence

Consolidated best-practice patterns from Impeccable's frontend-design skill. Use this as a quality filter: if your output violates any rule here, revise before delivering.

---

## AI Slop Anti-Patterns (Banned)

These are the fingerprints of AI-generated work from 2024–2025. If any appear in your output without deliberate justification, remove them.

**Typography**
- Inter, Roboto, Arial, Open Sans, Lato, Montserrat, Poppins as default fonts — they make every interface look the same
- Monospace typography as lazy shorthand for "technical/developer" vibes

**Color**
- Purple-to-blue gradients as a go-to aesthetic
- Neon accents on dark backgrounds — looks "cool" without real design decisions
- Cyan-on-dark as a default tech palette
- Gradient text on headings or metrics — decorative, not meaningful
- Gray text on colored backgrounds — looks washed out; use a darker shade of the background color instead
- Pure black (#000) or pure white (#fff) — always tint; they don't exist in nature
- Dark mode with glowing accents as the default choice

**Layout**
- Hero metric layout: big number + small label + supporting stats + gradient accent
- Identical card grids: icon + heading + text, repeated endlessly at the same size
- Wrapping everything in cards — not everything needs a container
- Cards nested inside cards
- Centering everything — left-aligned text with asymmetric layouts feels more designed
- Uniform spacing throughout — no rhythm, no hierarchy

**Visual Details**
- Glassmorphism used decoratively — blur/glass/glow borders without purposeful reason
- Rounded rectangle with thick colored border on one side — lazy accent
- Sparklines as decoration — tiny charts that look sophisticated but convey nothing
- Generic drop shadows on rounded rectangles — safe, forgettable
- Large rounded icons above every heading — templated, rarely adds value

**Motion**
- Bounce or elastic easing — felt trendy in 2015, now tacky and amateurish; real objects decelerate smoothly
- Animating everything — animation fatigue is real

**Interaction**
- Modals as the default for everything — modals are lazy; consider inline, drawer, popover, or undo patterns instead
- `outline: none` without a replacement focus indicator — accessibility violation

---

## Typography Excellence

### Modular Scale

Use fewer sizes with more contrast. A 5-size system covers most needs with a single ratio — pick one and commit:

| Ratio | Name | Character |
|-------|------|-----------|
| 1.25 | Major third | Subtle, refined |
| 1.333 | Perfect fourth | Balanced, versatile |
| 1.5 | Perfect fifth | Bold, editorial |

Never use sizes that are too close together (14px, 15px, 16px, 18px) — muddy hierarchy.

### Fluid vs Fixed Type

- **Fluid via `clamp()`**: Use for headings and display text on marketing/content pages. Format: `clamp(min, vw-value + rem-offset, max)` — the rem offset prevents collapse to 0.
- **Fixed `rem` scales**: Use for app UIs, dashboards, and data-dense interfaces. No major design system (Material, Polaris, Primer, Carbon) uses fluid type in product UI. Body text should also be fixed even on marketing pages.

### Font Alternatives

| Instead of | Use |
|------------|-----|
| Inter | Instrument Sans, Plus Jakarta Sans, Outfit |
| Roboto | Onest, Figtree, Urbanist |
| Open Sans | Source Sans 3, Nunito Sans, DM Sans |
| Generic sans | Fraunces, Newsreader, Lora (editorial/premium) |

One well-chosen font family in multiple weights often creates cleaner hierarchy than two competing typefaces.

### OpenType Features

Use these for polish — most developers skip them entirely:

```css
.data-table { font-variant-numeric: tabular-nums; }       /* Aligned numbers */
.recipe-amount { font-variant-numeric: diagonal-fractions; }
abbr { font-variant-caps: all-small-caps; }
code { font-variant-ligatures: none; }
body { font-kerning: normal; }
```

### Fallback Font Metric Matching (No FOUT/FOIT Shift)

```css
@font-face {
  font-family: 'CustomFont-Fallback';
  src: local('Arial');
  size-adjust: 105%;
  ascent-override: 90%;
  descent-override: 20%;
  line-gap-override: 10%;
}

body {
  font-family: 'CustomFont', 'CustomFont-Fallback', sans-serif;
}
```

Use [Fontaine](https://github.com/unjs/fontaine) to calculate these overrides automatically.

### Accessibility
- Never disable zoom (`user-scalable=no`)
- Use `rem`/`em` for font sizes — never `px` for body text
- Minimum 16px body text
- Line height scales inversely with line length — narrow columns need tighter leading, wide columns need more
- Increase line-height by 0.05–0.1 for light text on dark backgrounds (perceived weight is lighter)

---

## Color System (OKLCH)

### Why OKLCH Over HSL

OKLCH is perceptually uniform — equal steps in lightness *look* equal. HSL at 50% lightness looks bright in yellow and dark in blue. Use OKLCH for all new color work.

```css
--color-primary: oklch(60% 0.15 250);       /* Blue */
--color-primary-light: oklch(85% 0.08 250); /* Same hue, less chroma at high lightness */
--color-primary-dark: oklch(35% 0.12 250);
```

Key rule: as you move toward white or black, reduce chroma. High chroma at extreme lightness looks garish.

### Tinted Neutrals

Pure gray has no personality. Add a subtle brand-hue tint to all neutrals (chroma 0.01 — perceptible but not obvious):

```css
/* Dead */
--gray-100: oklch(95% 0 0);

/* Tinted toward warmth */
--gray-100: oklch(95% 0.01 60);

/* Tinted toward cool/tech */
--gray-100: oklch(95% 0.01 250);
```

### Two-Layer Token Architecture

Primitives stay constant; only the semantic layer changes for dark mode:

```css
/* Layer 1: Primitives */
--blue-500: oklch(60% 0.15 250);

/* Layer 2: Semantic */
--color-primary: var(--blue-500);

/* Dark mode: redefine semantic only */
[data-theme="dark"] {
  --color-primary: var(--blue-400);
}
```

### Dark Mode as Separate Design Decisions

Dark mode is not inverted light mode:

| Light Mode | Dark Mode |
|------------|-----------|
| Shadows for depth | Lighter surfaces for depth |
| Vibrant accents | Slightly desaturated accents |
| White backgrounds | Never pure black — use oklch(12-18% 0.01 hue) |
| Regular font weight | Reduce weight by ~50 units |

### 60-30-10 Visual Weight Rule

- **60%**: Neutral backgrounds, white space, base surfaces
- **30%**: Text, borders, inactive states
- **10%**: Accent — CTAs, highlights, focus states

Accent colors work *because* they're rare. Using the brand color everywhere kills its power.

### Modern CSS Color Functions

```css
/* Perceptually uniform mixing */
background: color-mix(in oklch, var(--color-primary) 20%, transparent);

/* Automatic light/dark without media query */
color: light-dark(oklch(20% 0.01 250), oklch(90% 0.01 250));
```

### Contrast Requirements (WCAG)

| Content | AA Minimum | AAA Target |
|---------|------------|------------|
| Body text | 4.5:1 | 7:1 |
| Large text (18px+ or 14px bold) | 3:1 | 4.5:1 |
| UI components, icons | 3:1 | 4.5:1 |
| Placeholder text | 4.5:1 | — |

---

## Motion Design Rules

### Duration Scale

| Duration | Use Case |
|----------|----------|
| 100–150ms | Micro-interactions: button press, toggle, color change |
| 200–300ms | State changes: menu open, tooltip, hover |
| 300–500ms | Layout changes: accordion, modal, drawer |
| 500–800ms | Entrance animations: page load, hero reveals |

Exit animations should be ~75% of the enter duration.

### Easing Curves

Never use the generic `ease` — it's a compromise that's rarely optimal.

```css
/* Entrances — smooth deceleration */
--ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);     /* Recommended default */
--ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);     /* Slightly more dramatic */
--ease-out-expo:  cubic-bezier(0.16, 1, 0.3, 1);      /* Snappy, confident */

/* Exits */
--ease-in: cubic-bezier(0.7, 0, 0.84, 0);

/* State toggles */
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
```

### Only Animate transform and opacity

Animating any layout property (width, height, padding, margin) causes layout recalculation. For height animations (accordions), use:

```css
.panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 300ms var(--ease-out-quart);
}
.panel.open {
  grid-template-rows: 1fr;
}
.panel > * { overflow: hidden; }
```

### CSS Stagger

```css
/* Parent sets animation */
.list-item {
  animation: fade-up 400ms var(--ease-out-quart) both;
  animation-delay: calc(var(--i) * 50ms);
}
```

Set `style="--i: 0"`, `--i: 1"`, etc. on each item. Cap total stagger time — 10 items × 50ms = 500ms max; reduce delay for larger lists.

### prefers-reduced-motion Is Non-Optional

Vestibular disorders affect ~35% of adults over 40.

```css
@media (prefers-reduced-motion: reduce) {
  .card { animation: fade-in 200ms ease-out; }  /* Crossfade, no spatial motion */
}

/* Or disable all non-essential motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
```

Preserve functional animations (progress bars, loading spinners) — just remove spatial movement.

---

## Interaction States (8 Required)

Every interactive element must have all eight states designed:

| State | When | Notes |
|-------|------|-------|
| Default | At rest | Base styling |
| Hover | Pointer over | Subtle lift, color shift — not touch |
| Focus | Keyboard/programmatic | Visible ring (see below) |
| Active | Being pressed | Pressed in, darker |
| Disabled | Not interactive | Reduced opacity, `cursor: not-allowed` |
| Loading | Processing | Spinner or skeleton |
| Error | Invalid state | Red border, icon, message |
| Success | Completed | Green check, confirmation |

The common miss: designing hover without focus, or vice versa. Keyboard users never see hover states.

### Focus Rings

Never `outline: none` without a replacement. Use `:focus-visible` to show focus rings only for keyboard users:

```css
button:focus { outline: none; }
button:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
```

Requirements: high contrast (3:1 minimum), 2–3px thick, offset from element, consistent across all interactive elements.

### Optimistic UI

Update immediately for low-stakes actions; sync later; rollback on failure. Do NOT use optimistic UI for payments or destructive operations.

### Native Modal & Overlay Patterns

```html
<!-- Modal: use native <dialog> with inert -->
<main inert><!-- content behind modal --></main>
<dialog open><!-- focus stays here; Escape closes --></dialog>

<!-- Non-modal overlays: use Popover API -->
<button popovertarget="menu">Open</button>
<div id="menu" popover><!-- light-dismiss, no z-index wars --></div>
```

### Undo Over Confirmation

Undo is better than confirmation dialogs — users click through confirmations mindlessly. Remove from UI immediately, show undo toast, actually delete after toast expires. Reserve confirmation dialogs for truly irreversible or high-cost actions.

### Keyboard Navigation

Use roving tabindex for component groups (tabs, menu items, radio groups): one item is tabbable at a time; arrow keys move within the group; Tab moves to the next component.

---

## Spatial Design

### 4pt Base System

8pt is too coarse — you'll frequently need 12px. Use 4pt increments: 4, 8, 12, 16, 24, 32, 48, 64, 96px. Name tokens semantically (`--space-sm`, `--space-lg`), not by value (`--spacing-8`).

### Semantic Spacing by Relationship

Spacing communicates grouping. Items that belong together get less space between them than items that are separate. Without rhythm, layouts feel monotonous. Use `gap` instead of margins for sibling spacing — eliminates margin collapse.

### Container Queries as Primary

Container queries are for components; viewport queries are for page layouts:

```css
.card-container { container-type: inline-size; }

@container (min-width: 400px) {
  .card { grid-template-columns: 120px 1fr; }
}
```

A card in a narrow sidebar stays compact, while the same card in a main content area expands — no viewport hacks.

### Squint Test for Hierarchy

Blur your eyes (or blur a screenshot). Can you still identify the most important element, the second most important, and clear groupings? If everything looks the same weight blurred, you have a hierarchy problem.

Hierarchy needs multiple dimensions simultaneously: size + weight + color + space. A heading that's larger, bolder, AND has more space above it.

### Cards Used Sparingly

Use cards only when content is truly distinct and actionable, items need visual comparison in a grid, or content needs clear interaction boundaries. Spacing and alignment create grouping naturally — cards are not required.

---

## UX Writing

### Button Labels

Specific verb + object, always. Never "OK", "Submit", "Yes/No":

| Bad | Good |
|-----|------|
| OK | Save changes |
| Submit | Create account |
| Yes | Delete message |
| Cancel | Keep editing |
| Click here | Download PDF |

For destructive actions, name the destruction: "Delete 5 items" not "Delete selected".

### Error Message Formula

Answer three questions in every error: (1) What happened? (2) Why? (3) How to fix it?

"Email address isn't valid. Please include an @ symbol." — not "Invalid input".

Never blame the user: "Please enter a date in MM/DD/YYYY format" — not "You entered an invalid date".

### Empty States as Onboarding

Acknowledge briefly → explain the value of filling it → provide a clear action. "No projects yet. Create your first one to get started." — not "No items".

### Translation Expansion Budget

Design with expansion space built in:

| Language | Expansion |
|----------|-----------|
| German | +30% |
| French | +20% |
| Finnish | +30–40% |
| Chinese | −30% (same pixel width) |

Keep numbers separate from strings. Use full sentences as single translation units (word order varies). Avoid abbreviations.

### Consistency

Pick one term and never vary it: Delete (not Remove/Trash), Settings (not Preferences/Options), Sign in (not Log in/Enter). Build a terminology glossary and enforce it — variety creates confusion.

---

## Improvement Modes

When the user wants targeted improvement, activate one of these modes:

| Mode | Intent | What to Do |
|------|--------|------------|
| **/distill** | Strip to essence | Remove decorative excess, tighten spacing, reduce color count, eliminate redundant copy |
| **/bolder** | Amplify timid design | Push contrast and scale, increase type sizes, strengthen color hierarchy, add decisive whitespace |
| **/quieter** | Tone down overcrowded design | Reduce noise, shrink secondary elements, dial back animation, simplify palette |
| **/harden** | Add robustness | Implement all 8 interaction states, i18n edge cases, empty states, error states, loading states, reduced-motion support |
| **/overdrive** | Push to technically extraordinary | WebGL/Canvas effects, View Transitions API, CSS spring physics, scroll-driven animations, complex orchestration |
| **/polish** | Pre-ship quality pass | Run through the full checklist: contrast, keyboard nav, focus rings, reduced motion, responsive breakpoints, font loading, token consistency |
| **/audit** | Comprehensive quality check | No edits — produce a report only, flagged by severity (critical / warning / suggestion) |


path: references/inspiration-patterns.md
content: # Inspiration Patterns Reference

Real design patterns from top design galleries, with Tailwind + shadcn/ui implementation code.

---

## Navbar Patterns

### Transparent-to-Solid on Scroll

```tsx
"use client";
import { useState, useEffect } from "react";
import { cn } from "@/lib/utils";

export function TransparentNavbar({ children }: { children: React.ReactNode }) {
  const [scrolled, setScrolled] = useState(false);

  useEffect(() => {
    const handleScroll = () => setScrolled(window.scrollY > 50);
    window.addEventListener("scroll", handleScroll, { passive: true });
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  return (
    <header className={cn(
      "fixed top-0 z-50 w-full transition-all duration-300",
      scrolled
        ? "border-b bg-background/90 backdrop-blur-md shadow-sm"
        : "bg-transparent"
    )}>
      <div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-4 sm:px-6">
        {children}
      </div>
    </header>
  );
}
```

### Centered Logo Navbar

```tsx
<header className="border-b">
  <div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-4 sm:px-6">
    <nav className="hidden gap-6 md:flex">
      <a href="#" className="text-sm text-muted-foreground hover:text-foreground">Products</a>
      <a href="#" className="text-sm text-muted-foreground hover:text-foreground">Solutions</a>
    </nav>
    <a href="/" className="absolute left-1/2 -translate-x-1/2 text-xl font-bold">Logo</a>
    <div className="hidden items-center gap-4 md:flex">
      <a href="#" className="text-sm text-muted-foreground hover:text-foreground">Sign in</a>
      <button className="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground">Get Started</button>
    </div>
  </div>
</header>
```

### Mega Menu

```tsx
"use client";
import { useState } from "react";
import { cn } from "@/lib/utils";

const megaMenuItems = {
  Products: [
    { title: "Analytics", desc: "Track user behavior", href: "#" },
    { title: "Automation", desc: "Workflows on autopilot", href: "#" },
    { title: "Security", desc: "Enterprise-grade protection", href: "#" },
    { title: "Integrations", desc: "Connect your stack", href: "#" },
  ],
};

export function MegaMenuNavbar() {
  const [open, setOpen] = useState<string | null>(null);

  return (
    <header className="relative border-b bg-background">
      <div className="mx-auto flex h-16 max-w-6xl items-center gap-8 px-4 sm:px-6">
        <span className="text-xl font-bold">Logo</span>
        <nav className="hidden md:flex">
          {Object.entries(megaMenuItems).map(([label, items]) => (
            <div key={label} onMouseEnter={() => setOpen(label)} onMouseLeave={() => setOpen(null)}>
              <button className="px-4 py-2 text-sm text-muted-foreground hover:text-foreground">{label}</button>
              <div className={cn(
                "absolute left-0 top-full w-full border-b bg-background shadow-lg transition-all duration-200",
                open === label ? "visible opacity-100" : "invisible opacity-0"
              )}>
                <div className="mx-auto grid max-w-6xl grid-cols-2 gap-4 p-6 lg:grid-cols-4">
                  {items.map((item) => (
                    <a key={item.title} href={item.href} className="rounded-lg p-4 hover:bg-muted transition-colors">
                      <p className="font-medium text-sm">{item.title}</p>
                      <p className="mt-1 text-xs text-muted-foreground">{item.desc}</p>
                    </a>
                  ))}
                </div>
              </div>
            </div>
          ))}
        </nav>
      </div>
    </header>
  );
}
```

### Mobile Drawer

```tsx
"use client";
import { useState } from "react";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { Menu } from "lucide-react";

export function MobileNav() {
  return (
    <Sheet>
      <SheetTrigger asChild>
        <button className="rounded-md p-2 hover:bg-muted md:hidden"><Menu className="h-5 w-5" /></button>
      </SheetTrigger>
      <SheetContent side="left" className="w-72 p-0">
        <div className="flex h-16 items-center border-b px-6">
          <span className="font-bold">Logo</span>
        </div>
        <nav className="flex flex-col p-4">
          <a href="#" className="rounded-lg px-4 py-3 text-sm font-medium hover:bg-muted">Home</a>
          <a href="#" className="rounded-lg px-4 py-3 text-sm font-medium hover:bg-muted">Products</a>
          <a href="#" className="rounded-lg px-4 py-3 text-sm font-medium hover:bg-muted">Pricing</a>
        </nav>
      </SheetContent>
    </Sheet>
  );
}
```

### Sticky with Reading Progress Bar

```tsx
"use client";
import { useScroll, useSpring, motion } from "framer-motion";

export function ProgressNavbar({ children }: { children: React.ReactNode }) {
  const { scrollYProgress } = useScroll();
  const scaleX = useSpring(scrollYProgress, { stiffness: 100, damping: 30 });

  return (
    <header className="sticky top-0 z-50 border-b bg-background/80 backdrop-blur-sm">
      <div className="mx-auto flex h-16 max-w-6xl items-center px-4 sm:px-6">{children}</div>
      <motion.div className="h-0.5 bg-primary origin-left" style={{ scaleX }} />
    </header>
  );
}
```

---

## CTA Patterns

### Full-Bleed CTA

```tsx
<section className="bg-primary px-4 py-20 text-center text-primary-foreground sm:px-6">
  <div className="mx-auto max-w-2xl">
    <h2 className="text-3xl font-bold sm:text-4xl">Ready to get started?</h2>
    <p className="mx-auto mt-4 max-w-lg opacity-90">Join thousands of developers who ship faster.</p>
    <div className="mt-8 flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
      <button className="w-full rounded-lg bg-background px-8 py-3 font-medium text-foreground sm:w-auto">Start Free Trial</button>
      <button className="w-full rounded-lg border border-primary-foreground/30 px-8 py-3 font-medium sm:w-auto">Talk to Sales</button>
    </div>
  </div>
</section>
```

### Inline CTA (within content)

```tsx
<div className="my-12 rounded-2xl border bg-gradient-to-r from-primary/5 to-accent/5 p-8 text-center sm:p-12">
  <h3 className="text-xl font-bold">Want to dive deeper?</h3>
  <p className="mt-2 text-muted-foreground">Get the complete guide with 50+ examples.</p>
  <button className="mt-6 rounded-lg bg-primary px-6 py-2.5 text-sm font-medium text-primary-foreground">Download Free Guide</button>
</div>
```

### Floating CTA

```tsx
"use client";
import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";

export function FloatingCTA() {
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    const handleScroll = () => setVisible(window.scrollY > 600);
    window.addEventListener("scroll", handleScroll, { passive: true });
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  return (
    <AnimatePresence>
      {visible && (
        <motion.div
          initial={{ y: 100, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          exit={{ y: 100, opacity: 0 }}
          className="fixed bottom-6 right-6 z-40"
        >
          <button className="flex items-center gap-2 rounded-full bg-primary px-6 py-3 text-sm font-medium text-primary-foreground shadow-lg hover:shadow-xl transition-shadow">
            Get Started Free
          </button>
        </motion.div>
      )}
    </AnimatePresence>
  );
}
```

### Social Proof CTA

```tsx
<section className="border-y bg-muted/50 px-4 py-16 text-center sm:px-6">
  <div className="mx-auto max-w-2xl">
    {/* Avatars */}
    <div className="flex justify-center -space-x-3">
      {[1,2,3,4,5].map(i => (
        <div key={i} className="h-10 w-10 rounded-full border-2 border-background bg-muted" />
      ))}
      <div className="flex h-10 w-10 items-center justify-center rounded-full border-2 border-background bg-primary text-xs font-bold text-primary-foreground">+2k</div>
    </div>
    <p className="mt-4 text-sm text-muted-foreground">Trusted by 2,000+ engineering teams worldwide</p>
    <h3 className="mt-4 text-2xl font-bold">Start shipping faster today</h3>
    <button className="mt-6 rounded-lg bg-primary px-8 py-3 font-medium text-primary-foreground">Start Free &mdash; No Credit Card</button>
    <p className="mt-3 text-xs text-muted-foreground">14-day free trial. Cancel anytime.</p>
  </div>
</section>
```

---

## Landing Page Patterns

### Above-the-Fold Hook (Badge + H1 + Social Proof)

```tsx
<section className="mx-auto max-w-6xl px-4 pt-20 text-center sm:px-6 lg:pt-32">
  <span className="inline-flex items-center gap-2 rounded-full border bg-muted px-3 py-1 text-xs font-medium">
    <span className="h-1.5 w-1.5 rounded-full bg-emerald-500" /> Now in public beta
  </span>
  <h1 className="mx-auto mt-6 max-w-4xl text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl">
    Ship features, not <span className="text-primary">infrastructure</span>
  </h1>
  <p className="mx-auto mt-6 max-w-2xl text-lg text-muted-foreground">
    The developer platform that handles the boring stuff so you can focus on building.
  </p>
  <div className="mt-8 flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
    <button className="w-full rounded-lg bg-primary px-8 py-3 font-medium text-primary-foreground sm:w-auto">Get Started Free</button>
    <button className="w-full rounded-lg border px-8 py-3 font-medium sm:w-auto">See Demo</button>
  </div>
  {/* Social proof strip */}
  <div className="mt-12 flex flex-wrap items-center justify-center gap-8 opacity-50 grayscale">
    {["Vercel", "Stripe", "Linear", "Notion"].map(name => (
      <span key={name} className="text-sm font-medium">{name}</span>
    ))}
  </div>
</section>
```

### Feature Grid (Bento Style)

```tsx
<section className="mx-auto max-w-6xl px-4 py-20 sm:px-6">
  <h2 className="text-center text-3xl font-bold">Everything you need</h2>
  <div className="mt-12 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
    {/* Large feature card */}
    <div className="row-span-2 rounded-2xl border bg-card p-8">
      <div className="mb-6 h-48 rounded-xl bg-muted" />
      <h3 className="text-lg font-semibold">Primary Feature</h3>
      <p className="mt-2 text-sm text-muted-foreground">Detailed description of the main feature.</p>
    </div>
    {/* Small feature cards */}
    <div className="rounded-2xl border bg-card p-6">
      <div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">A</div>
      <h3 className="font-semibold">Feature B</h3>
      <p className="mt-1 text-sm text-muted-foreground">Brief description.</p>
    </div>
    <div className="rounded-2xl border bg-card p-6">
      <div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">C</div>
      <h3 className="font-semibold">Feature C</h3>
      <p className="mt-1 text-sm text-muted-foreground">Brief description.</p>
    </div>
    {/* Wide feature card */}
    <div className="rounded-2xl border bg-card p-6 sm:col-span-2">
      <h3 className="font-semibold">Wide Feature</h3>
      <p className="mt-1 text-sm text-muted-foreground">This feature spans two columns.</p>
    </div>
  </div>
</section>
```

### Pricing Toggle (Monthly/Annual)

```tsx
"use client";
import { useState } from "react";
import { cn } from "@/lib/utils";

export function PricingToggle() {
  const [annual, setAnnual] = useState(false);

  return (
    <div>
      <div className="flex items-center justify-center gap-3">
        <span className={cn("text-sm", !annual ? "font-medium" : "text-muted-foreground")}>Monthly</span>
        <button
          onClick={() => setAnnual(!annual)}
          className={cn("relative h-6 w-11 rounded-full transition-colors", annual ? "bg-primary" : "bg-muted")}
        >
          <span className={cn(
            "absolute top-0.5 h-5 w-5 rounded-full bg-white shadow-sm transition-transform",
            annual ? "translate-x-5" : "translate-x-0.5"
          )} />
        </button>
        <span className={cn("text-sm", annual ? "font-medium" : "text-muted-foreground")}>
          Annual <span className="ml-1 rounded-full bg-emerald-100 px-2 py-0.5 text-xs text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400">Save 20%</span>
        </span>
      </div>
      <div className="mt-8 text-center">
        <span className="text-5xl font-bold">${annual ? "19" : "24"}</span>
        <span className="text-muted-foreground">/mo</span>
      </div>
    </div>
  );
}
```

---

## SaaS Patterns

### Empty State

```tsx
import { Plus, FileText } from "lucide-react";

export function EmptyState() {
  return (
    <div className="flex flex-col items-center justify-center rounded-xl border border-dashed bg-muted/30 py-16">
      <div className="flex h-12 w-12 items-center justify-center rounded-full bg-muted">
        <FileText className="h-6 w-6 text-muted-foreground" />
      </div>
      <h3 className="mt-4 text-sm font-semibold">No documents yet</h3>
      <p className="mt-1 text-sm text-muted-foreground">Get started by creating your first document.</p>
      <button className="mt-6 inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground">
        <Plus className="h-4 w-4" /> New Document
      </button>
    </div>
  );
}
```

### Settings Page Structure

```tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";

export function SettingsPage() {
  return (
    <div className="mx-auto max-w-4xl space-y-6 p-6">
      <div>
        <h1 className="text-2xl font-bold">Settings</h1>
        <p className="text-muted-foreground">Manage your account preferences.</p>
      </div>
      <Tabs defaultValue="general" className="space-y-6">
        <TabsList>
          <TabsTrigger value="general">General</TabsTrigger>
          <TabsTrigger value="billing">Billing</TabsTrigger>
          <TabsTrigger value="team">Team</TabsTrigger>
        </TabsList>
        <TabsContent value="general">
          <Card>
            <CardHeader>
              <CardTitle>Profile</CardTitle>
              <CardDescription>Update your personal information.</CardDescription>
            </CardHeader>
            <CardContent className="space-y-4">
              <div className="grid gap-4 sm:grid-cols-2">
                <div className="space-y-2">
                  <Label htmlFor="name">Name</Label>
                  <Input id="name" defaultValue="Jane Doe" />
                </div>
                <div className="space-y-2">
                  <Label htmlFor="email">Email</Label>
                  <Input id="email" type="email" defaultValue="[REDACTED_SECRET]" />
                </div>
              </div>
              <Button>Save Changes</Button>
            </CardContent>
          </Card>
        </TabsContent>
      </Tabs>
      {/* Danger zone */}
      <Card className="border-destructive/50">
        <CardHeader>
          <CardTitle className="text-destructive">Danger Zone</CardTitle>
          <CardDescription>Irreversible and destructive actions.</CardDescription>
        </CardHeader>
        <CardContent>
          <Button variant="destructive">Delete Account</Button>
        </CardContent>
      </Card>
    </div>
  );
}
```

---

## Animation Patterns

### Page Transition (Framer Motion + Next.js App Router)

```tsx
// app/template.tsx
"use client";
import { motion } from "framer-motion";

export default function Template({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 8 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.3, ease: "easeOut" }}
    >
      {children}
    </motion.div>
  );
}
```

### Micro-Interaction: Button with Loading State

```tsx
"use client";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Loader2, Check } from "lucide-react";

export function LoadingButton({ children, onClick }: { children: React.ReactNode; onClick: () => Promise<void> }) {
  const [state, setState] = useState<"idle" | "loading" | "success">("idle");

  const handleClick = async () => {
    setState("loading");
    await onClick();
    setState("success");
    setTimeout(() => setState("idle"), 1500);
  };

  return (
    <button
      onClick={handleClick}
      disabled={state !== "idle"}
      className="relative inline-flex h-10 items-center justify-center rounded-lg bg-primary px-6 text-sm font-medium text-primary-foreground disabled:opacity-80"
    >
      <AnimatePresence mode="wait">
        {state === "idle" && <motion.span key="idle" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>{children}</motion.span>}
        {state === "loading" && <motion.span key="loading" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}><Loader2 className="h-4 w-4 animate-spin" /></motion.span>}
        {state === "success" && <motion.span key="success" initial={{ opacity: 0, scale: 0.5 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0 }}><Check className="h-4 w-4" /></motion.span>}
      </AnimatePresence>
    </button>
  );
}
```

### Scroll-Driven Narrative (Progress-based sections)

```tsx
"use client";
import { useRef } from "react";
import { useScroll, useTransform, motion } from "framer-motion";

const steps = [
  { title: "Step 1", description: "First thing happens" },
  { title: "Step 2", description: "Then this" },
  { title: "Step 3", description: "Finally this" },
];

export function ScrollNarrative() {
  const containerRef = useRef(null);
  const { scrollYProgress } = useScroll({ target: containerRef, offset: ["start start", "end end"] });

  return (
    <div ref={containerRef} className="relative" style={{ height: `${steps.length * 100}vh` }}>
      <div className="sticky top-0 flex h-screen items-center">
        <div className="mx-auto max-w-4xl px-4 sm:px-6">
          {steps.map((step, i) => {
            const start = i / steps.length;
            const end = (i + 1) / steps.length;
            return (
              <ScrollStep key={i} progress={scrollYProgress} start={start} end={end} step={step} />
            );
          })}
        </div>
      </div>
    </div>
  );
}

function ScrollStep({ progress, start, end, step }: any) {
  const opacity = useTransform(progress, [start, start + 0.1, end - 0.1, end], [0, 1, 1, 0]);
  const y = useTransform(progress, [start, start + 0.1, end - 0.1, end], [40, 0, 0, -40]);

  return (
    <motion.div style={{ opacity, y }} className="absolute inset-0 flex flex-col items-center justify-center text-center">
      <h2 className="text-4xl font-bold">{step.title}</h2>
      <p className="mt-4 text-lg text-muted-foreground">{step.description}</p>
    </motion.div>
  );
}
```

---

## Design System Patterns

### Token Architecture (CSS Variables + Tailwind)

```css
/* globals.css */
@layer base {
  :root {
    /* Spacing scale */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-6: 1.5rem;
    --space-8: 2rem;

    /* Radius scale */
    --radius-sm: 0.375rem;
    --radius-md: 0.5rem;
    --radius-lg: 0.75rem;
    --radius-xl: 1rem;
    --radius-2xl: 1.5rem;

    /* Shadows */
    --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
    --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1);
    --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1);

    /* Colors (HSL for alpha support) */
    --background: 0 0% 100%;
    --foreground: 222 47% 11%;
    --primary: 221 83% 53%;
    --muted: 210 40% 96%;
    --border: 214 32% 91%;
  }
  .dark {
    --background: 222 47% 4%;
    --foreground: 210 40% 98%;
    --primary: 217 91% 60%;
    --muted: 217 33% 17%;
    --border: 217 19% 27%;
  }
}
```

### Variant System (CVA Pattern)

```tsx
// lib/variants.ts
import { cva } from "class-variance-authority";

export const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
        secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        sm: "h-8 px-3 text-xs",
        md: "h-10 px-4 text-sm",
        lg: "h-12 px-6 text-base",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: { variant: "default", size: "md" },
  }
);

export const inputVariants = cva(
  "flex w-full rounded-md border bg-background px-3 text-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
  {
    variants: {
      size: {
        sm: "h-8 text-xs",
        md: "h-10",
        lg: "h-12 text-base",
      },
      state: {
        default: "border-input",
        error: "border-destructive focus-visible:ring-destructive",
        success: "border-emerald-500 focus-visible:ring-emerald-500",
      },
    },
    defaultVariants: { size: "md", state: "default" },
  }
);
```

### Component Documentation Pattern

When building a design system, document each component inline:

```tsx
/**
 * Badge - Displays a small status indicator.
 *
 * @variant default - Primary brand color fill
 * @variant secondary - Subtle muted background
 * @variant destructive - Red for errors/danger
 * @variant outline - Border only, transparent fill
 * @variant success - Green for positive states
 * @variant warning - Amber for caution states
 *
 * @size sm - Compact (10px text)
 * @size md - Standard (12px text)
 * @size lg - Prominent (14px text)
 *
 * @example
 * <Badge variant="success" size="sm">Active</Badge>
 * <Badge variant="warning">Pending</Badge>
 */
```

---

## Quick Reference: Pattern to File Mapping

| Pattern | See Also |
|---------|----------|
| Navbar | `layout-patterns.md` (all layouts include navbars) |
| CTA | `templates.md` (SaaS Landing, Marketing) |
| Feature Grid | `layout-patterns.md` (SaaS Landing) |
| Empty State | `templates.md` (Dashboard) |
| Color Tokens | `color-palettes.md` |
| Animations | `animation-recipes.md` |
| Forms | `component-patterns.md` (Form pattern) |
| Data Tables | `component-patterns.md` (TanStack Table) |


path: references/layout-patterns.md
content: # Layout Patterns Reference

Full responsive layout structures with Tailwind CSS. Each layout is production-ready with proper breakpoint handling.

---

## 1. SaaS Landing Page

```tsx
export default function SaaSLanding() {
  return (
    <div className="min-h-screen bg-background">
      {/* Navbar */}
      <header className="sticky top-0 z-50 border-b bg-background/80 backdrop-blur-sm">
        <div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-4 sm:px-6">
          <span className="text-xl font-bold">Logo</span>
          <nav className="hidden gap-6 md:flex">
            <a href="#features" className="text-sm text-muted-foreground hover:text-foreground">Features</a>
            <a href="#pricing" className="text-sm text-muted-foreground hover:text-foreground">Pricing</a>
          </nav>
          <div className="flex items-center gap-3">
            <button className="text-sm text-muted-foreground hover:text-foreground">Log in</button>
            <button className="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground">Get Started</button>
          </div>
        </div>
      </header>

      {/* Hero */}
      <section className="mx-auto max-w-6xl px-4 py-20 text-center sm:px-6 lg:py-32">
        <span className="mb-4 inline-block rounded-full border px-3 py-1 text-xs font-medium">New: AI features</span>
        <h1 className="mx-auto max-w-3xl text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl">
          Build better products, faster
        </h1>
        <p className="mx-auto mt-6 max-w-2xl text-lg text-muted-foreground">
          Description text that explains the value proposition in one or two sentences.
        </p>
        <div className="mt-8 flex flex-col items-center gap-3 sm:flex-row sm:justify-center">
          <button className="w-full rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground sm:w-auto">Start Free Trial</button>
          <button className="w-full rounded-lg border px-6 py-3 font-medium sm:w-auto">Watch Demo</button>
        </div>
      </section>

      {/* Features Grid */}
      <section id="features" className="mx-auto max-w-6xl px-4 py-20 sm:px-6">
        <h2 className="text-center text-3xl font-bold">Features</h2>
        <p className="mx-auto mt-4 max-w-2xl text-center text-muted-foreground">Subheading text</p>
        <div className="mt-12 grid gap-8 sm:grid-cols-2 lg:grid-cols-3">
          {/* Repeat feature cards */}
          <div className="rounded-xl border bg-card p-6">
            <div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">Icon</div>
            <h3 className="font-semibold">Feature Title</h3>
            <p className="mt-2 text-sm text-muted-foreground">Feature description text.</p>
          </div>
        </div>
      </section>

      {/* Pricing */}
      <section id="pricing" className="bg-muted/50 px-4 py-20 sm:px-6">
        <div className="mx-auto max-w-6xl text-center">
          <h2 className="text-3xl font-bold">Pricing</h2>
          <div className="mt-12 grid gap-8 sm:grid-cols-2 lg:grid-cols-3">
            {/* Pricing card */}
            <div className="rounded-xl border bg-card p-8 text-left">
              <h3 className="font-semibold">Pro</h3>
              <div className="mt-4"><span className="text-4xl font-bold">$29</span><span className="text-muted-foreground">/mo</span></div>
              <ul className="mt-6 space-y-3 text-sm">
                <li className="flex items-center gap-2"><span className="text-primary">&#10003;</span> Feature one</li>
              </ul>
              <button className="mt-8 w-full rounded-lg bg-primary py-2.5 text-sm font-medium text-primary-foreground">Get Started</button>
            </div>
          </div>
        </div>
      </section>

      {/* Testimonials */}
      <section className="mx-auto max-w-6xl px-4 py-20 sm:px-6">
        <h2 className="text-center text-3xl font-bold">What people say</h2>
        <div className="mt-12 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
          <blockquote className="rounded-xl border bg-card p-6">
            <p className="text-sm text-muted-foreground">"Quote text here."</p>
            <footer className="mt-4 flex items-center gap-3">
              <div className="h-10 w-10 rounded-full bg-muted" />
              <div><p className="text-sm font-medium">Name</p><p className="text-xs text-muted-foreground">Title</p></div>
            </footer>
          </blockquote>
        </div>
      </section>

      {/* CTA */}
      <section className="bg-primary px-4 py-20 text-center text-primary-foreground sm:px-6">
        <h2 className="text-3xl font-bold">Ready to get started?</h2>
        <p className="mx-auto mt-4 max-w-xl opacity-90">Call to action supporting text.</p>
        <button className="mt-8 rounded-lg bg-background px-8 py-3 font-medium text-foreground">Start Free Trial</button>
      </section>

      {/* Footer */}
      <footer className="border-t px-4 py-12 sm:px-6">
        <div className="mx-auto grid max-w-6xl gap-8 sm:grid-cols-2 lg:grid-cols-4">
          <div><h4 className="font-semibold">Product</h4><ul className="mt-4 space-y-2 text-sm text-muted-foreground"><li>Features</li><li>Pricing</li></ul></div>
          <div><h4 className="font-semibold">Company</h4><ul className="mt-4 space-y-2 text-sm text-muted-foreground"><li>About</li><li>Blog</li></ul></div>
        </div>
        <div className="mx-auto mt-12 max-w-6xl border-t pt-8 text-center text-sm text-muted-foreground">
          &copy; 2026 Company. All rights reserved.
        </div>
      </footer>
    </div>
  );
}
```

---

## 2. Dashboard (Collapsible Sidebar + Header + Content)

```tsx
"use client";
import { useState } from "react";
import { cn } from "@/lib/utils";
import { PanelLeft } from "lucide-react";

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  const [collapsed, setCollapsed] = useState(false);

  return (
    <div className="flex h-screen overflow-hidden bg-background">
      {/* Sidebar */}
      <aside className={cn(
        "hidden flex-col border-r bg-card transition-all duration-300 md:flex",
        collapsed ? "w-16" : "w-64"
      )}>
        <div className="flex h-16 items-center justify-between border-b px-4">
          {!collapsed && <span className="font-bold">App</span>}
          <button onClick={() => setCollapsed(!collapsed)} className="rounded-md p-1.5 hover:bg-muted">
            <PanelLeft className="h-5 w-5" />
          </button>
        </div>
        <nav className="flex-1 space-y-1 p-3">
          {/* Nav items */}
          <a href="#" className="flex items-center gap-3 rounded-lg bg-muted px-3 py-2 text-sm font-medium">
            <span className="h-4 w-4 shrink-0">D</span>
            {!collapsed && <span>Dashboard</span>}
          </a>
          <a href="#" className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm text-muted-foreground hover:bg-muted">
            <span className="h-4 w-4 shrink-0">A</span>
            {!collapsed && <span>Analytics</span>}
          </a>
        </nav>
      </aside>

      {/* Main area */}
      <div className="flex flex-1 flex-col overflow-hidden">
        {/* Header */}
        <header className="flex h-16 items-center justify-between border-b px-4 lg:px-6">
          <button className="rounded-md p-1.5 hover:bg-muted md:hidden"><PanelLeft className="h-5 w-5" /></button>
          <div className="flex-1" />
          <div className="flex items-center gap-3">
            <div className="h-8 w-8 rounded-full bg-muted" />
          </div>
        </header>

        {/* Content */}
        <main className="flex-1 overflow-y-auto p-4 lg:p-6">
          {/* Stats row */}
          <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
            <div className="rounded-xl border bg-card p-6">
              <p className="text-sm text-muted-foreground">Revenue</p>
              <p className="mt-1 text-2xl font-bold">$12,345</p>
            </div>
          </div>
          {/* Main grid */}
          <div className="mt-6 grid gap-6 lg:grid-cols-[1fr_350px]">
            <div className="rounded-xl border bg-card p-6">Chart area</div>
            <div className="rounded-xl border bg-card p-6">Activity feed</div>
          </div>
          {children}
        </main>
      </div>
    </div>
  );
}
```

---

## 3. Portfolio (Masonry Grid with Filter)

```tsx
"use client";
import { useState } from "react";
import { cn } from "@/lib/utils";

const categories = ["All", "Web", "Mobile", "Branding"];
const projects = [
  { title: "Project A", category: "Web", tall: true },
  { title: "Project B", category: "Mobile", tall: false },
  { title: "Project C", category: "Branding", tall: true },
  { title: "Project D", category: "Web", tall: false },
  { title: "Project E", category: "Mobile", tall: true },
  { title: "Project F", category: "Branding", tall: false },
];

export default function PortfolioGrid() {
  const [active, setActive] = useState("All");
  const filtered = active === "All" ? projects : projects.filter((p) => p.category === active);

  return (
    <section className="mx-auto max-w-6xl px-4 py-20 sm:px-6">
      <h2 className="text-3xl font-bold">Work</h2>
      {/* Filter bar */}
      <div className="mt-8 flex flex-wrap gap-2">
        {categories.map((cat) => (
          <button
            key={cat}
            onClick={() => setActive(cat)}
            className={cn(
              "rounded-full px-4 py-1.5 text-sm font-medium transition-colors",
              active === cat ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"
            )}
          >
            {cat}
          </button>
        ))}
      </div>
      {/* Masonry grid via CSS columns */}
      <div className="mt-10 columns-1 gap-4 sm:columns-2 lg:columns-3">
        {filtered.map((project, i) => (
          <div key={i} className={cn(
            "mb-4 break-inside-avoid overflow-hidden rounded-xl border bg-card",
            project.tall ? "h-80" : "h-52"
          )}>
            <div className="flex h-full flex-col justify-end bg-gradient-to-t from-black/60 to-transparent p-6">
              <span className="text-xs font-medium text-white/70">{project.category}</span>
              <h3 className="mt-1 text-lg font-semibold text-white">{project.title}</h3>
            </div>
          </div>
        ))}
      </div>
    </section>
  );
}
```

---

## 4. Blog (Readable Content Width with TOC Sidebar)

```tsx
export default function BlogLayout() {
  return (
    <div className="mx-auto max-w-6xl px-4 py-12 sm:px-6">
      <div className="grid gap-10 lg:grid-cols-[1fr_250px]">
        {/* Article */}
        <article className="prose prose-neutral dark:prose-invert max-w-none lg:max-w-[680px]">
          <h1>Article Title</h1>
          <div className="flex items-center gap-3 text-sm text-muted-foreground not-prose">
            <div className="h-8 w-8 rounded-full bg-muted" />
            <span>Author Name</span>
            <span>&middot;</span>
            <time>Mar 21, 2026</time>
          </div>
          <p>Article body content goes here. The prose utility from @tailwindcss/typography handles all inner element spacing and typography.</p>
          <h2 id="section-1">Section 1</h2>
          <p>Paragraph content...</p>
          <h2 id="section-2">Section 2</h2>
          <p>Paragraph content...</p>
        </article>

        {/* TOC Sidebar */}
        <aside className="hidden lg:block">
          <div className="sticky top-24">
            <h4 className="mb-4 text-sm font-semibold">On this page</h4>
            <nav className="space-y-2 text-sm">
              <a href="#section-1" className="block text-muted-foreground hover:text-foreground">Section 1</a>
              <a href="#section-2" className="block text-muted-foreground hover:text-foreground">Section 2</a>
            </nav>
          </div>
        </aside>
      </div>
    </div>
  );
}
```

---

## 5. E-commerce (Product Grid + Filters + Cart Drawer)

```tsx
"use client";
import { useState } from "react";
import { cn } from "@/lib/utils";
import { X, SlidersHorizontal } from "lucide-react";

export default function EcommerceLayout() {
  const [filtersOpen, setFiltersOpen] = useState(false);
  const [cartOpen, setCartOpen] = useState(false);

  return (
    <div className="min-h-screen bg-background">
      {/* Top bar */}
      <header className="sticky top-0 z-40 flex h-16 items-center justify-between border-b bg-background/80 px-4 backdrop-blur-sm sm:px-6">
        <span className="text-xl font-bold">Store</span>
        <button onClick={() => setCartOpen(true)} className="relative rounded-md p-2 hover:bg-muted">
          Cart <span className="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full bg-primary text-[10px] text-primary-foreground">3</span>
        </button>
      </header>

      <div className="mx-auto flex max-w-7xl gap-6 px-4 py-8 sm:px-6">
        {/* Filter sidebar (desktop) */}
        <aside className="hidden w-60 shrink-0 lg:block">
          <h3 className="mb-4 font-semibold">Filters</h3>
          <div className="space-y-6">
            <div>
              <h4 className="mb-2 text-sm font-medium">Category</h4>
              <div className="space-y-1.5 text-sm text-muted-foreground">
                <label className="flex items-center gap-2"><input type="checkbox" className="rounded" /> Shoes</label>
                <label className="flex items-center gap-2"><input type="checkbox" className="rounded" /> Jackets</label>
              </div>
            </div>
            <div>
              <h4 className="mb-2 text-sm font-medium">Price Range</h4>
              <input type="range" className="w-full" />
            </div>
          </div>
        </aside>

        {/* Mobile filter toggle */}
        <button
          onClick={() => setFiltersOpen(true)}
          className="fixed bottom-4 right-4 z-30 flex items-center gap-2 rounded-full bg-primary px-4 py-3 text-sm font-medium text-primary-foreground shadow-lg lg:hidden"
        >
          <SlidersHorizontal className="h-4 w-4" /> Filters
        </button>

        {/* Product grid */}
        <div className="flex-1">
          <div className="mb-6 flex items-center justify-between">
            <p className="text-sm text-muted-foreground">128 products</p>
            <select className="rounded-md border px-3 py-1.5 text-sm">
              <option>Sort by: Newest</option>
              <option>Price: Low to High</option>
            </select>
          </div>
          <div className="grid gap-4 grid-cols-2 md:grid-cols-3 xl:grid-cols-4">
            {Array.from({ length: 8 }).map((_, i) => (
              <div key={i} className="group overflow-hidden rounded-xl border bg-card">
                <div className="aspect-square bg-muted transition-transform group-hover:scale-105" />
                <div className="p-4">
                  <h3 className="text-sm font-medium">Product Name</h3>
                  <p className="mt-1 text-sm font-semibold">$99.00</p>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* Cart Drawer */}
      {cartOpen && (
        <div className="fixed inset-0 z-50 flex justify-end">
          <div className="absolute inset-0 bg-black/40" onClick={() => setCartOpen(false)} />
          <div className="relative w-full max-w-md bg-background p-6 shadow-xl">
            <div className="flex items-center justify-between">
              <h3 className="text-lg font-semibold">Cart (3)</h3>
              <button onClick={() => setCartOpen(false)}><X className="h-5 w-5" /></button>
            </div>
            <div className="mt-6 flex-1 space-y-4">
              {/* Cart item */}
              <div className="flex gap-4">
                <div className="h-20 w-20 rounded-lg bg-muted" />
                <div className="flex-1">
                  <p className="text-sm font-medium">Product</p>
                  <p className="text-sm text-muted-foreground">$99.00</p>
                </div>
              </div>
            </div>
            <button className="mt-8 w-full rounded-lg bg-primary py-3 text-sm font-medium text-primary-foreground">Checkout &mdash; $297.00</button>
          </div>
        </div>
      )}
    </div>
  );
}
```

---

## 6. Marketing (Full-Bleed Alternating Sections)

```tsx
export default function MarketingPage() {
  return (
    <div className="min-h-screen">
      {/* Full-bleed hero */}
      <section className="relative flex min-h-[80vh] items-center justify-center bg-gradient-to-br from-primary to-primary/60 px-4 text-center text-primary-foreground">
        <div className="max-w-3xl">
          <h1 className="text-4xl font-bold sm:text-6xl lg:text-7xl">Bold Statement</h1>
          <p className="mx-auto mt-6 max-w-xl text-lg opacity-90">Supporting text for the hero section.</p>
          <button className="mt-8 rounded-full bg-background px-8 py-3 font-medium text-foreground">Get Started</button>
        </div>
      </section>

      {/* Alternating feature: image left */}
      <section className="mx-auto grid max-w-6xl items-center gap-12 px-4 py-20 sm:px-6 lg:grid-cols-2">
        <div className="aspect-[4/3] rounded-2xl bg-muted" />
        <div>
          <span className="text-sm font-medium text-primary">Feature</span>
          <h2 className="mt-2 text-3xl font-bold">Feature headline</h2>
          <p className="mt-4 text-muted-foreground">Feature description that explains the benefit.</p>
        </div>
      </section>

      {/* Alternating feature: image right */}
      <section className="bg-muted/50">
        <div className="mx-auto grid max-w-6xl items-center gap-12 px-4 py-20 sm:px-6 lg:grid-cols-2">
          <div className="order-2 lg:order-1">
            <span className="text-sm font-medium text-primary">Feature</span>
            <h2 className="mt-2 text-3xl font-bold">Another feature headline</h2>
            <p className="mt-4 text-muted-foreground">Another description.</p>
          </div>
          <div className="order-1 aspect-[4/3] rounded-2xl bg-muted lg:order-2" />
        </div>
      </section>

      {/* Stats strip */}
      <section className="border-y bg-card px-4 py-16 sm:px-6">
        <div className="mx-auto grid max-w-4xl grid-cols-2 gap-8 text-center lg:grid-cols-4">
          <div><p className="text-3xl font-bold">10K+</p><p className="mt-1 text-sm text-muted-foreground">Users</p></div>
          <div><p className="text-3xl font-bold">99.9%</p><p className="mt-1 text-sm text-muted-foreground">Uptime</p></div>
          <div><p className="text-3xl font-bold">50+</p><p className="mt-1 text-sm text-muted-foreground">Countries</p></div>
          <div><p className="text-3xl font-bold">4.9</p><p className="mt-1 text-sm text-muted-foreground">Rating</p></div>
        </div>
      </section>

      {/* Full-bleed CTA */}
      <section className="bg-foreground px-4 py-20 text-center text-background">
        <h2 className="text-3xl font-bold">Ready to transform your workflow?</h2>
        <button className="mt-8 rounded-full bg-background px-8 py-3 font-medium text-foreground">Start Free Trial</button>
      </section>
    </div>
  );
}
```

---

## Breakpoint Quick Reference

| Prefix | Min Width | Common Use |
|--------|-----------|------------|
| `sm:` | 640px | Stack to 2-col |
| `md:` | 768px | Show sidebar nav |
| `lg:` | 1024px | 3-col grids, full layouts |
| `xl:` | 1280px | Wide content areas |
| `2xl:` | 1536px | Max-width containers |

**Pattern:** Start mobile-first, add breakpoints going up. Use `max-w-6xl mx-auto px-4 sm:px-6` as the standard content container.


path: references/templates.md
content: # Page Templates Reference

10 ready-to-use page templates with component trees, key classes, responsive strategies, animation entry points, and dark mode notes.

---

## 1. SaaS Landing

**Component Tree:**
```
Page
  Navbar (sticky, backdrop-blur)
    Logo | NavLinks (hidden md:flex) | CTAButton
  Hero
    Badge | H1 | Subtitle | ButtonGroup (col sm:row)
  LogoCloud (grayscale opacity-60)
  Features (3-col grid)
    FeatureCard[] (icon + title + desc)
  HowItWorks (numbered steps, alternating layout)
  Testimonials (3-col masonry)
  Pricing (3-col, center highlighted)
  FAQ (Accordion)
  CTA (full-bleed primary bg)
  Footer (4-col grid)
```

**Key Classes:** `max-w-6xl mx-auto px-4 sm:px-6`, hero `py-20 lg:py-32 text-center`, features grid `grid gap-8 sm:grid-cols-2 lg:grid-cols-3`

**Responsive:** Stack everything on mobile. 2-col at `sm:`, 3-col at `lg:`. Hero buttons stack `flex-col` then `sm:flex-row`.

**Animation Points:** Hero badge fade-in, feature cards stagger on scroll, testimonials fade-up, stats count-up, CTA scale on hover.

**Dark Mode:** Use `bg-background`/`text-foreground` CSS vars. Cards use `bg-card`. Highlighted pricing card: `ring-2 ring-primary`.

---

## 2. Dashboard

**Component Tree:**
```
Layout (flex h-screen)
  Sidebar (w-64, collapsible to w-16)
    Logo | NavItems[] | UserMenu
  Main (flex-1 flex-col overflow-hidden)
    Header (h-16, border-b)
      MobileMenuTrigger | SearchBar | NotificationBell | Avatar
    Content (overflow-y-auto p-6)
      StatsRow (4-col grid)
      MainGrid (chart + sidebar: grid lg:grid-cols-[1fr_350px])
      DataTable
```

**Key Classes:** Sidebar `transition-all duration-300`, content `flex-1 overflow-y-auto`, stats `grid gap-4 sm:grid-cols-2 lg:grid-cols-4`

**Responsive:** Sidebar hidden on mobile, sheet/drawer trigger. Stats 1-col -> 2-col -> 4-col. Main grid stacks below `lg:`.

**Animation Points:** Sidebar collapse slide, stat cards number count, chart fade-in, table row hover highlight.

**Dark Mode:** Sidebar `bg-card`, active nav `bg-primary text-primary-foreground`, cards `bg-card border`.

---

## 3. Portfolio

**Component Tree:**
```
Page
  Navbar (minimal, transparent)
  Hero (name + title + short bio)
  FilterBar (category buttons)
  ProjectGrid (masonry or uniform grid)
    ProjectCard[] (image + overlay on hover)
  About (split: image left, text right)
  Contact (simple form or email CTA)
  Footer (social links)
```

**Key Classes:** Masonry `columns-1 sm:columns-2 lg:columns-3 gap-4`, cards `break-inside-avoid mb-4`, overlay `opacity-0 group-hover:opacity-100 transition-opacity`

**Responsive:** 1-col masonry on mobile. Filter bar horizontal scroll with `overflow-x-auto flex gap-2 pb-2`.

**Animation Points:** Project cards scale on hover, image reveal on scroll, filter transition with layout animation, hero text stagger.

**Dark Mode:** Use `bg-background`. Project overlays: `bg-black/70` works in both modes. Hover state: `group-hover:shadow-xl`.

---

## 4. Blog

**Component Tree:**
```
Layout
  Navbar
  Main (max-w-6xl, grid lg:grid-cols-[1fr_250px])
    Article (prose dark:prose-invert)
      Title | Meta (avatar, author, date) | Body | Tags
    TOCSidebar (sticky top-24, hidden lg:block)
      TOCLinks[]
  NewsletterCTA (full-width banner)
  RelatedPosts (3-col grid)
  Footer
```

**Key Classes:** Article `prose prose-neutral dark:prose-invert max-w-none lg:max-w-[680px]`, TOC `sticky top-24`

**Responsive:** TOC hidden below `lg:`, article full width. Related posts 1-col -> 3-col.

**Animation Points:** TOC active link highlight on scroll (Intersection Observer), newsletter CTA slide-up, reading progress bar in header.

**Dark Mode:** `prose-invert` handles article. TOC links `text-muted-foreground hover:text-foreground`. Code blocks get `bg-zinc-900`.

---

## 5. Marketing

**Component Tree:**
```
Page
  Navbar (transparent, scrolls to solid)
  Hero (full-bleed gradient, centered)
  LogoStrip
  AlternatingFeatures[] (image + text, flip order)
  StatsStrip (4-col centered numbers)
  Testimonial (single large quote, centered)
  CTA (dark bg, contrasting button)
  Footer
```

**Key Classes:** Full-bleed `w-full`, constrained inner `max-w-6xl mx-auto px-4 sm:px-6`, alternating `lg:grid-cols-2`, flip `order-2 lg:order-1`

**Responsive:** Alternating features stack. Image always on top on mobile (use `order-` classes). Stats 2-col -> 4-col.

**Animation Points:** Hero parallax, feature images slide-in from sides, stats count-up, testimonial fade, CTA button pulse.

**Dark Mode:** Gradient hero use `from-primary to-primary/60`. Dark sections `bg-foreground text-background`. Stats `bg-card`.

---

## 6. E-commerce Product

**Component Tree:**
```
Page
  Breadcrumbs
  ProductSection (grid lg:grid-cols-2 gap-8)
    ImageGallery (main + thumbnails)
    ProductInfo
      Title | Price | Rating | Description | VariantSelector | QuantityInput | AddToCartButton
  Tabs (Description | Reviews | Shipping)
  RelatedProducts (4-col scroll on mobile)
  Footer
```

**Key Classes:** Gallery `aspect-square rounded-xl overflow-hidden`, thumbnails `grid grid-cols-4 gap-2`, product info `space-y-4`

**Responsive:** Image gallery above product info on mobile. Thumbnails 4-col always. Related products horizontal scroll `flex gap-4 overflow-x-auto snap-x` on mobile, grid on desktop.

**Animation Points:** Image zoom on hover, thumbnail swap fade, add-to-cart button bounce, variant selector highlight, related product cards hover lift.

**Dark Mode:** Product images unaffected. Cards `bg-card`. Price `text-foreground`. Sale price `text-destructive`.

---

## 7. Auth Pages (Login/Register)

**Component Tree:**
```
Page (min-h-screen flex)
  LeftPanel (hidden lg:flex, brand/illustration)
  RightPanel (flex items-center justify-center)
    AuthCard (w-full max-w-sm)
      Logo | Title | Subtitle
      SocialButtons (Google, GitHub)
      Divider ("or continue with")
      Form (email + password inputs)
      SubmitButton
      FooterLink ("Don't have an account?")
```

**Key Classes:** Split layout `grid lg:grid-cols-2 min-h-screen`, right panel `flex items-center justify-center p-8`, form card `w-full max-w-sm space-y-6`

**Responsive:** Left panel hidden below `lg:`. Form centered with `mx-auto`.

**Animation Points:** Form fields stagger-in, social buttons hover scale, submit button loading spinner, error shake.

**Dark Mode:** Left panel can use dark gradient regardless. Form `bg-card` with `border`. Input `bg-background`.

---

## 8. Settings

**Component Tree:**
```
DashboardLayout
  SettingsPage
    Header (title + description)
    Navigation (tabs or sidebar)
      TabsList (horizontal sm, vertical lg)
    Content
      SettingsSection[]
        SectionTitle | SectionDescription
        SettingsForm (fields in a card)
          FormField[] | SaveButton
      DangerZone (destructive bordered card)
```

**Key Classes:** Settings nav `flex gap-1 overflow-x-auto sm:flex-col sm:w-48`, content `flex-1 space-y-6`, section card `rounded-xl border p-6`

**Responsive:** Tabs horizontal scroll on mobile, vertical sidebar on desktop. Form fields full-width on mobile, 2-col `sm:grid-cols-2` on desktop.

**Animation Points:** Tab switch content fade, save button success checkmark, danger zone expand, toast notification on save.

**Dark Mode:** Danger zone `border-destructive/50 bg-destructive/5`. Standard cards `bg-card`.

---

## 9. Pricing Page

**Component Tree:**
```
Page
  Navbar
  Hero (title + subtitle + billing toggle)
  PricingGrid (3-col, center highlighted)
    PricingCard[]
      PlanName | Price | BillingPeriod | FeatureList | CTAButton
  ComparisonTable (hidden on mobile, scrollable)
  FAQ (Accordion)
  CTA (full-bleed)
  Footer
```

**Key Classes:** Pricing grid `grid gap-8 sm:grid-cols-2 lg:grid-cols-3`, highlighted card `ring-2 ring-primary scale-105`, toggle `inline-flex rounded-full bg-muted p-1`

**Responsive:** 1-col -> 2-col -> 3-col. Comparison table horizontal scroll on mobile with `overflow-x-auto`. FAQ full-width.

**Animation Points:** Price number morph on billing toggle, card hover lift, feature list stagger, FAQ accordion spring.

**Dark Mode:** Highlighted card `bg-primary/5 ring-primary`. Feature checks `text-primary`. Disabled features `text-muted-foreground line-through`.

---

## 10. 404 Error

**Component Tree:**
```
Page (min-h-screen flex items-center justify-center)
  Container (max-w-md text-center)
    Illustration (SVG or Lottie animation)
    ErrorCode (text-7xl font-bold text-muted-foreground/20)
    Title ("Page not found")
    Description (text-muted-foreground)
    ButtonGroup
      HomeButton (primary) | BackButton (outline)
    SearchBar (optional)
```

**Key Classes:** `min-h-screen flex items-center justify-center px-4`, error code `text-8xl font-bold text-muted-foreground/20`, buttons `flex flex-col sm:flex-row gap-3 mt-8`

**Responsive:** Everything centered, naturally responsive. Buttons stack on mobile.

**Animation Points:** Illustration loop, error code fade-in, buttons stagger, search bar expand on focus.

**Dark Mode:** Error code `text-muted-foreground/10` (more subtle). Illustration should use currentColor or CSS vars. Background `bg-background`.

---

## Universal Patterns

**Container:** `mx-auto max-w-6xl px-4 sm:px-6 lg:px-8`

**Section spacing:** `py-16 sm:py-20 lg:py-24`

**Card:** `rounded-xl border bg-card p-6 shadow-sm`

**Heading hierarchy:** `text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight`

**Body text:** `text-muted-foreground text-base lg:text-lg max-w-2xl`


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)