# SkillPatch skill: frontend-ui-dark-ts

A comprehensive skill for building dark-themed React applications using TypeScript, Tailwind CSS, and Framer Motion. It provides a full project scaffold including directory structure, configuration files, UI components (buttons, cards, dialogs, etc.), glassmorphism effects, and animation patterns. Ideal for creating dashboards, admin panels, and data-rich interfaces with a refined dark aesthetic.

You (the agent) were given this URL and asked to use this skill. This is a **public** skill — no SkillPatch account, API key, or prior setup is required. Two ways to use it:

**1. Use it right now** — the skill's complete file tree (SKILL.md + all reference files) is inlined below; read `SKILL.md` first, then follow it, consulting the other files as it directs.

**2. Install the exact package onto disk** (recommended if you can run a shell — this reproduces the skill byte-for-byte, including any binary assets that can't be inlined):

```bash
mkdir -p .claude/skills/frontend-ui-dark-ts
curl -sSL https://skillpatch.dev/install_skill/frontend-ui-dark-ts | tar -xz -C .claude/skills/
```

(`.claude/skills/` is Claude Code's convention; use whatever directory your agent loads skills from.)


---

## Skill files (10)

- `SKILL.md`
- `assets/foundry-logo-dark.png`
- `assets/foundry-logo-light.png`
- `assets/Segoe UI Bold Italic.ttf`
- `assets/Segoe UI Bold.ttf`
- `assets/Segoe UI Italic.ttf`
- `assets/Segoe UI.ttf`
- `references/components.md`
- `references/design-tokens.md`
- `references/patterns.md`


### `SKILL.md`

````markdown
---
name: frontend-ui-dark-ts
description: Build dark-themed React applications using Tailwind CSS with custom theming, glassmorphism effects, and Framer Motion animations. Use when creating dashboards, admin panels, or data-rich interfaces with a refined dark aesthetic.
license: MIT
metadata:
  author: Microsoft
  version: "1.0.0"
---

# Frontend UI Dark Theme (TypeScript)

A modern dark-themed React UI system using **Tailwind CSS** and **Framer Motion**. Designed for dashboards, admin panels, and data-rich applications with glassmorphism effects and tasteful animations.

## Stack

| Package | Version | Purpose |
|---------|---------|---------|
| `react` | ^18.x | UI framework |
| `react-dom` | ^18.x | DOM rendering |
| `react-router-dom` | ^6.x | Routing |
| `framer-motion` | ^11.x | Animations |
| `clsx` | ^2.x | Class merging |
| `tailwindcss` | ^3.x | Styling |
| `vite` | ^5.x | Build tool |
| `typescript` | ^5.x | Type safety |

## Quick Start

```bash
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install framer-motion clsx react-router-dom
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

## Project Structure

```
public/
├── favicon.ico                    # Classic favicon (32x32)
├── favicon.svg                    # Modern SVG favicon
├── apple-touch-icon.png           # iOS home screen (180x180)
├── og-image.png                   # Social sharing image (1200x630)
└── site.webmanifest               # PWA manifest
src/
├── assets/
│   └── fonts/
│       ├── Segoe UI.ttf
│       ├── Segoe UI Bold.ttf
│       ├── Segoe UI Italic.ttf
│       └── Segoe UI Bold Italic.ttf
├── components/
│   ├── ui/
│   │   ├── Button.tsx
│   │   ├── Card.tsx
│   │   ├── Input.tsx
│   │   ├── Badge.tsx
│   │   ├── Dialog.tsx
│   │   ├── Tabs.tsx
│   │   └── index.ts
│   └── layout/
│       ├── AppShell.tsx
│       ├── Sidebar.tsx
│       └── PageHeader.tsx
├── styles/
│   └── globals.css
├── App.tsx
└── main.tsx
```

## Configuration

### index.html

The HTML entry point with mobile viewport, favicons, and social meta tags:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
    
    <!-- Favicons -->
    <link rel="icon" href="/favicon.ico" sizes="32x32" />
    <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
    <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
    <link rel="manifest" href="/site.webmanifest" />
    
    <!-- Theme color for mobile browser chrome -->
    <meta name="theme-color" content="#18181B" />
    
    <!-- Open Graph -->
    <meta property="og:type" content="website" />
    <meta property="og:title" content="App Name" />
    <meta property="og:description" content="App description" />
    <meta property="og:image" content="https://example.com/og-image.png" />
    <meta property="og:url" content="https://example.com" />
    
    <!-- Twitter Card -->
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:title" content="App Name" />
    <meta name="twitter:description" content="App description" />
    <meta name="twitter:image" content="https://example.com/og-image.png" />
    
    <title>App Name</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
```

### public/site.webmanifest

PWA manifest for installable web apps:

```json
{
  "name": "App Name",
  "short_name": "App",
  "icons": [
    { "src": "/favicon.ico", "sizes": "32x32", "type": "image/x-icon" },
    { "src": "/apple-touch-icon.png", "sizes": "180x180", "type": "image/png" }
  ],
  "theme_color": "#18181B",
  "background_color": "#18181B",
  "display": "standalone"
}
```

### tailwind.config.js

```js
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {
      fontFamily: {
        sans: ['Segoe UI', 'system-ui', 'sans-serif'],
      },
      colors: {
        brand: {
          DEFAULT: '#8251EE',
          hover: '#9366F5',
          light: '#A37EF5',
          subtle: 'rgba(130, 81, 238, 0.15)',
        },
        neutral: {
          bg1: 'hsl(240, 6%, 10%)',
          bg2: 'hsl(240, 5%, 12%)',
          bg3: 'hsl(240, 5%, 14%)',
          bg4: 'hsl(240, 4%, 18%)',
          bg5: 'hsl(240, 4%, 22%)',
          bg6: 'hsl(240, 4%, 26%)',
        },
        text: {
          primary: '#FFFFFF',
          secondary: '#A1A1AA',
          muted: '#71717A',
        },
        border: {
          subtle: 'hsla(0, 0%, 100%, 0.08)',
          DEFAULT: 'hsla(0, 0%, 100%, 0.12)',
          strong: 'hsla(0, 0%, 100%, 0.20)',
        },
        status: {
          success: '#10B981',
          warning: '#F59E0B',
          error: '#EF4444',
          info: '#3B82F6',
        },
        dataviz: {
          purple: '#8251EE',
          blue: '#3B82F6',
          green: '#10B981',
          yellow: '#F59E0B',
          red: '#EF4444',
          pink: '#EC4899',
          cyan: '#06B6D4',
        },
      },
      borderRadius: {
        DEFAULT: '0.5rem',
        lg: '0.75rem',
        xl: '1rem',
      },
      boxShadow: {
        glow: '0 0 20px rgba(130, 81, 238, 0.3)',
        'glow-lg': '0 0 40px rgba(130, 81, 238, 0.4)',
      },
      backdropBlur: {
        xs: '2px',
      },
      animation: {
        'fade-in': 'fadeIn 0.3s ease-out',
        'slide-up': 'slideUp 0.3s ease-out',
        'slide-down': 'slideDown 0.3s ease-out',
      },
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
        slideUp: {
          '0%': { opacity: '0', transform: 'translateY(10px)' },
          '100%': { opacity: '1', transform: 'translateY(0)' },
        },
        slideDown: {
          '0%': { opacity: '0', transform: 'translateY(-10px)' },
          '100%': { opacity: '1', transform: 'translateY(0)' },
        },
      },
      // Mobile: safe area insets for notched devices
      spacing: {
        'safe-top': 'env(safe-area-inset-top)',
        'safe-bottom': 'env(safe-area-inset-bottom)',
        'safe-left': 'env(safe-area-inset-left)',
        'safe-right': 'env(safe-area-inset-right)',
      },
      // Mobile: minimum touch target sizes (44px per Apple/Google guidelines)
      minHeight: {
        'touch': '44px',
      },
      minWidth: {
        'touch': '44px',
      },
    },
  },
  plugins: [],
};
```

### postcss.config.js

```js
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};
```

### src/styles/globals.css

```css
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Font faces */
@font-face {
  font-family: 'Segoe UI';
  src: url('../assets/fonts/Segoe UI.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Segoe UI';
  src: url('../assets/fonts/Segoe UI Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Segoe UI';
  src: url('../assets/fonts/Segoe UI Italic.ttf') format('truetype');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: 'Segoe UI';
  src: url('../assets/fonts/Segoe UI Bold Italic.ttf') format('truetype');
  font-weight: 700;
  font-style: italic;
  font-display: swap;
}

/* CSS Custom Properties */
:root {
  /* Brand colors */
  --color-brand: #8251EE;
  --color-brand-hover: #9366F5;
  --color-brand-light: #A37EF5;
  --color-brand-subtle: rgba(130, 81, 238, 0.15);

  /* Neutral backgrounds */
  --color-bg-1: hsl(240, 6%, 10%);
  --color-bg-2: hsl(240, 5%, 12%);
  --color-bg-3: hsl(240, 5%, 14%);
  --color-bg-4: hsl(240, 4%, 18%);
  --color-bg-5: hsl(240, 4%, 22%);
  --color-bg-6: hsl(240, 4%, 26%);

  /* Text colors */
  --color-text-primary: #FFFFFF;
  --color-text-secondary: #A1A1AA;
  --color-text-muted: #71717A;

  /* Border colors */
  --color-border-subtle: hsla(0, 0%, 100%, 0.08);
  --color-border-default: hsla(0, 0%, 100%, 0.12);
  --color-border-strong: hsla(0, 0%, 100%, 0.20);

  /* Status colors */
  --color-success: #10B981;
  --color-warning: #F59E0B;
  --color-error: #EF4444;
  --color-info: #3B82F6;

  /* Spacing */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  --spacing-2xl: 3rem;

  /* Border radius */
  --radius-sm: 0.375rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-xl: 1rem;

  /* Transitions */
  --transition-fast: 150ms ease;
  --transition-normal: 200ms ease;
  --transition-slow: 300ms ease;
}

/* Base styles */
html {
  color-scheme: dark;
}

body {
  @apply bg-neutral-bg1 text-text-primary font-sans antialiased;
  min-height: 100vh;
}

/* Focus styles */
*:focus-visible {
  @apply outline-none ring-2 ring-brand ring-offset-2 ring-offset-neutral-bg1;
}

/* Scrollbar styling */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

::-webkit-scrollbar-track {
  @apply bg-neutral-bg2;
}

::-webkit-scrollbar-thumb {
  @apply bg-neutral-bg5 rounded-full;
}

::-webkit-scrollbar-thumb:hover {
  @apply bg-neutral-bg6;
}

/* Glass utility classes */
@layer components {
  .glass {
    @apply backdrop-blur-md bg-white/5 border border-white/10;
  }

  .glass-card {
    @apply backdrop-blur-md bg-white/5 border border-white/10 rounded-xl;
  }

  .glass-panel {
    @apply backdrop-blur-lg bg-black/40 border border-white/5;
  }

  .glass-overlay {
    @apply backdrop-blur-sm bg-black/60;
  }

  .glass-input {
    @apply backdrop-blur-sm bg-white/5 border border-white/10 focus:border-brand focus:bg-white/10;
  }
}

/* Animation utilities */
@layer utilities {
  .animate-in {
    animation: fadeIn 0.3s ease-out, slideUp 0.3s ease-out;
  }
}
```

### src/main.tsx

```tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './styles/globals.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);
```

### src/App.tsx

```tsx
import { Routes, Route } from 'react-router-dom';
import { AnimatePresence } from 'framer-motion';
import { AppShell } from './components/layout/AppShell';
import { Dashboard } from './pages/Dashboard';
import { Settings } from './pages/Settings';

export default function App() {
  return (
    <AppShell>
      <AnimatePresence mode="wait">
        <Routes>
          <Route path="/" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </AnimatePresence>
    </AppShell>
  );
}
```

## Animation Patterns

### Framer Motion Variants

```tsx
// Fade in on mount
export const fadeIn = {
  initial: { opacity: 0 },
  animate: { opacity: 1 },
  exit: { opacity: 0 },
  transition: { duration: 0.2 },
};

// Slide up on mount
export const slideUp = {
  initial: { opacity: 0, y: 20 },
  animate: { opacity: 1, y: 0 },
  exit: { opacity: 0, y: 20 },
  transition: { duration: 0.3, ease: 'easeOut' },
};

// Scale on hover (for buttons/cards)
export const scaleOnHover = {
  whileHover: { scale: 1.02 },
  whileTap: { scale: 0.98 },
  transition: { type: 'spring', stiffness: 400, damping: 17 },
};

// Stagger children
export const staggerContainer = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.05,
      delayChildren: 0.1,
    },
  },
};

export const staggerItem = {
  hidden: { opacity: 0, y: 10 },
  visible: {
    opacity: 1,
    y: 0,
    transition: { duration: 0.2, ease: 'easeOut' },
  },
};
```

### Page Transition Wrapper

```tsx
import { motion } from 'framer-motion';
import { ReactNode } from 'react';

interface PageTransitionProps {
  children: ReactNode;
}

export function PageTransition({ children }: PageTransitionProps) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: -20 }}
      transition={{ duration: 0.3, ease: 'easeOut' }}
    >
      {children}
    </motion.div>
  );
}
```

## Glass Effect Patterns

### Glass Card

```tsx
<div className="glass-card p-6">
  <h2 className="text-lg font-semibold text-text-primary">Card Title</h2>
  <p className="text-text-secondary mt-2">Card content goes here.</p>
</div>
```

### Glass Panel (Sidebar)

```tsx
<aside className="glass-panel w-64 h-screen p-4">
  <nav className="space-y-2">
    {/* Navigation items */}
  </nav>
</aside>
```

### Glass Modal Overlay

```tsx
<motion.div
  className="fixed inset-0 glass-overlay flex items-center justify-center z-50"
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  exit={{ opacity: 0 }}
>
  <motion.div
    className="glass-card p-6 max-w-md w-full mx-4"
    initial={{ scale: 0.95, opacity: 0 }}
    animate={{ scale: 1, opacity: 1 }}
    exit={{ scale: 0.95, opacity: 0 }}
  >
    {/* Modal content */}
  </motion.div>
</motion.div>
```

## Typography

| Element | Classes |
|---------|---------|
| Page title | `text-2xl font-semibold text-text-primary` |
| Section title | `text-lg font-semibold text-text-primary` |
| Card title | `text-base font-medium text-text-primary` |
| Body text | `text-sm text-text-secondary` |
| Caption | `text-xs text-text-muted` |
| Label | `text-sm font-medium text-text-secondary` |

## Color Usage

| Use Case | Color | Class |
|----------|-------|-------|
| Primary action | Brand purple | `bg-brand text-white` |
| Primary hover | Brand hover | `hover:bg-brand-hover` |
| Page background | Neutral bg1 | `bg-neutral-bg1` |
| Card background | Neutral bg2 | `bg-neutral-bg2` |
| Elevated surface | Neutral bg3 | `bg-neutral-bg3` |
| Input background | Neutral bg2 | `bg-neutral-bg2` |
| Input focus | Neutral bg3 | `focus:bg-neutral-bg3` |
| Border default | Border default | `border-border` |
| Border subtle | Border subtle | `border-border-subtle` |
| Success | Status success | `text-status-success` |
| Warning | Status warning | `text-status-warning` |
| Error | Status error | `text-status-error` |

## Related Files

- [Design Tokens](./references/design-tokens.md) — Complete color system, spacing, typography scales
- [Components](./references/components.md) — Button, Card, Input, Dialog, Tabs, and more
- [Patterns](./references/patterns.md) — Page layouts, navigation, lists, forms

````


### `assets/foundry-logo-dark.png`

```
<binary file omitted>
```


### `assets/foundry-logo-light.png`

```
<binary file omitted>
```


### `assets/Segoe UI Bold Italic.ttf`

```
<binary file omitted>
```


### `assets/Segoe UI Bold.ttf`

```
<binary file omitted>
```


### `assets/Segoe UI Italic.ttf`

```
<binary file omitted>
```


### `assets/Segoe UI.ttf`

```
<binary file omitted>
```


### `references/components.md`

````markdown
# Components

Reusable UI components built with React, Tailwind CSS, and Framer Motion.

## Button

```tsx
import { motion, type HTMLMotionProps } from 'framer-motion';
import { clsx } from 'clsx';
import { forwardRef, type ReactNode } from 'react';

type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
type ButtonSize = 'sm' | 'md' | 'lg';

interface ButtonProps extends Omit<HTMLMotionProps<'button'>, 'children'> {
  variant?: ButtonVariant;
  size?: ButtonSize;
  children: ReactNode;
  isLoading?: boolean;
  leftIcon?: ReactNode;
  rightIcon?: ReactNode;
}

const variants: Record<ButtonVariant, string> = {
  primary: 'bg-brand text-white hover:bg-brand-hover active:bg-brand-light',
  secondary: 'bg-neutral-bg3 text-text-primary border border-border hover:bg-neutral-bg4 hover:border-border-strong',
  ghost: 'text-text-secondary hover:text-text-primary hover:bg-neutral-bg3',
  danger: 'bg-status-error text-white hover:bg-red-600 active:bg-red-700',
};

// Size variants - all sizes meet 44px minimum height on mobile for touch targets
const sizes: Record<ButtonSize, string> = {
  sm: 'h-8 min-h-touch px-3 text-xs gap-1.5 rounded',      // 32px desktop, 44px mobile minimum
  md: 'h-10 min-h-touch px-4 text-sm gap-2 rounded-lg',    // 40px desktop, 44px mobile minimum
  lg: 'h-12 px-6 text-base gap-2.5 rounded-lg',            // 48px already exceeds 44px
};

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ variant = 'primary', size = 'md', children, isLoading, leftIcon, rightIcon, className, disabled, ...props }, ref) => {
    return (
      <motion.button
        ref={ref}
        whileHover={{ scale: disabled || isLoading ? 1 : 1.02 }}
        whileTap={{ scale: disabled || isLoading ? 1 : 0.98 }}
        transition={{ type: 'spring', stiffness: 400, damping: 17 }}
        className={clsx(
          'inline-flex items-center justify-center font-medium transition-colors duration-150',
          'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-bg1',
          'disabled:opacity-50 disabled:cursor-not-allowed',
          variants[variant],
          sizes[size],
          className
        )}
        disabled={disabled || isLoading}
        {...props}
      >
        {isLoading ? (
          <svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
            <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
            <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
          </svg>
        ) : (
          <>
            {leftIcon && <span className="flex-shrink-0">{leftIcon}</span>}
            {children}
            {rightIcon && <span className="flex-shrink-0">{rightIcon}</span>}
          </>
        )}
      </motion.button>
    );
  }
);

Button.displayName = 'Button';
```

### Button Usage

```tsx
// Primary (default)
<Button>Save Changes</Button>

// Secondary
<Button variant="secondary">Cancel</Button>

// Ghost
<Button variant="ghost">Learn More</Button>

// Danger
<Button variant="danger">Delete</Button>

// With icons
<Button leftIcon={<PlusIcon />}>Add Item</Button>
<Button rightIcon={<ArrowRightIcon />}>Continue</Button>

// Loading state
<Button isLoading>Saving...</Button>

// Sizes
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
```

---

## Input

```tsx
import { forwardRef, type InputHTMLAttributes, type ReactNode } from 'react';
import { clsx } from 'clsx';

interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
  label?: string;
  error?: string;
  hint?: string;
  leftIcon?: ReactNode;
  rightIcon?: ReactNode;
}

export const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ label, error, hint, leftIcon, rightIcon, className, id, ...props }, ref) => {
    const inputId = id || label?.toLowerCase().replace(/\s+/g, '-');

    return (
      <div className="flex flex-col gap-1.5">
        {label && (
          <label htmlFor={inputId} className="text-sm font-medium text-text-secondary">
            {label}
          </label>
        )}
        <div className="relative">
          {leftIcon && (
            <span className="absolute left-3 top-1/2 -translate-y-1/2 text-text-muted">
              {leftIcon}
            </span>
          )}
          <input
            ref={ref}
            id={inputId}
            className={clsx(
              'w-full h-10 min-h-touch px-3 rounded-lg text-sm text-text-primary', // min-h-touch for 44px mobile touch target
              'bg-neutral-bg2 border border-border',
              'placeholder:text-text-muted',
              'focus:outline-none focus:border-brand focus:bg-neutral-bg3',
              'disabled:opacity-50 disabled:cursor-not-allowed',
              'transition-colors duration-150',
              leftIcon && 'pl-10',
              rightIcon && 'pr-10',
              error && 'border-status-error focus:border-status-error',
              className
            )}
            {...props}
          />
          {rightIcon && (
            <span className="absolute right-3 top-1/2 -translate-y-1/2 text-text-muted">
              {rightIcon}
            </span>
          )}
        </div>
        {error && <p className="text-xs text-status-error">{error}</p>}
        {hint && !error && <p className="text-xs text-text-muted">{hint}</p>}
      </div>
    );
  }
);

Input.displayName = 'Input';
```

### Search Input Variant

```tsx
import { MagnifyingGlassIcon, XMarkIcon } from '@heroicons/react/20/solid';

interface SearchInputProps {
  value: string;
  onChange: (value: string) => void;
  placeholder?: string;
}

export function SearchInput({ value, onChange, placeholder = 'Search...' }: SearchInputProps) {
  return (
    <Input
      type="search"
      value={value}
      onChange={(e) => onChange(e.target.value)}
      placeholder={placeholder}
      leftIcon={<MagnifyingGlassIcon className="w-4 h-4" />}
      rightIcon={
        value && (
          <button onClick={() => onChange('')} className="hover:text-text-primary transition-colors">
            <XMarkIcon className="w-4 h-4" />
          </button>
        )
      }
    />
  );
}
```

---

## Card

```tsx
import { motion, type HTMLMotionProps } from 'framer-motion';
import { clsx } from 'clsx';
import { forwardRef, type ReactNode } from 'react';

type CardVariant = 'default' | 'glass' | 'elevated';

interface CardProps extends HTMLMotionProps<'div'> {
  variant?: CardVariant;
  children: ReactNode;
  interactive?: boolean;
  padding?: 'none' | 'sm' | 'md' | 'lg';
}

const variants: Record<CardVariant, string> = {
  default: 'bg-neutral-bg2 border border-border',
  glass: 'backdrop-blur-md bg-white/5 border border-white/10',
  elevated: 'bg-neutral-bg3 border border-border shadow-lg',
};

const paddings = {
  none: '',
  sm: 'p-3',
  md: 'p-4',
  lg: 'p-6',
};

export const Card = forwardRef<HTMLDivElement, CardProps>(
  ({ variant = 'default', children, interactive = false, padding = 'md', className, ...props }, ref) => {
    const baseClass = clsx(
      'rounded-xl',
      variants[variant],
      paddings[padding],
      interactive && 'cursor-pointer hover:border-border-strong transition-colors duration-150',
      className
    );

    if (interactive) {
      return (
        <motion.div
          ref={ref}
          className={baseClass}
          whileHover={{ scale: 1.01, y: -2 }}
          whileTap={{ scale: 0.99 }}
          transition={{ type: 'spring', stiffness: 400, damping: 25 }}
          {...props}
        >
          {children}
        </motion.div>
      );
    }

    return (
      <motion.div ref={ref} className={baseClass} {...props}>
        {children}
      </motion.div>
    );
  }
);

Card.displayName = 'Card';

// Subcomponents
export function CardHeader({ children, className }: { children: ReactNode; className?: string }) {
  return <div className={clsx('mb-4', className)}>{children}</div>;
}

export function CardTitle({ children, className }: { children: ReactNode; className?: string }) {
  return <h3 className={clsx('text-lg font-semibold text-text-primary', className)}>{children}</h3>;
}

export function CardDescription({ children, className }: { children: ReactNode; className?: string }) {
  return <p className={clsx('text-sm text-text-secondary mt-1', className)}>{children}</p>;
}

export function CardContent({ children, className }: { children: ReactNode; className?: string }) {
  return <div className={className}>{children}</div>;
}

export function CardFooter({ children, className }: { children: ReactNode; className?: string }) {
  return <div className={clsx('mt-4 flex items-center gap-2', className)}>{children}</div>;
}
```

### Card Usage

```tsx
// Default card
<Card>
  <CardHeader>
    <CardTitle>Card Title</CardTitle>
    <CardDescription>Card description goes here</CardDescription>
  </CardHeader>
  <CardContent>
    <p>Card content</p>
  </CardContent>
</Card>

// Glass card
<Card variant="glass">
  <p>Glass card with blur effect</p>
</Card>

// Interactive card
<Card interactive onClick={() => console.log('clicked')}>
  <p>Click me!</p>
</Card>
```

---

## Badge

```tsx
import { clsx } from 'clsx';
import { type ReactNode } from 'react';

type BadgeVariant = 'default' | 'brand' | 'success' | 'warning' | 'error' | 'info';
type BadgeSize = 'sm' | 'md';

interface BadgeProps {
  variant?: BadgeVariant;
  size?: BadgeSize;
  children: ReactNode;
  className?: string;
}

const variants: Record<BadgeVariant, string> = {
  default: 'bg-neutral-bg4 text-text-secondary',
  brand: 'bg-brand-subtle text-brand-light',
  success: 'bg-status-success-subtle text-status-success',
  warning: 'bg-status-warning-subtle text-status-warning',
  error: 'bg-status-error-subtle text-status-error',
  info: 'bg-status-info-subtle text-status-info',
};

const sizes: Record<BadgeSize, string> = {
  sm: 'text-xs px-1.5 py-0.5',
  md: 'text-xs px-2 py-1',
};

export function Badge({ variant = 'default', size = 'md', children, className }: BadgeProps) {
  return (
    <span
      className={clsx(
        'inline-flex items-center font-medium rounded-md',
        variants[variant],
        sizes[size],
        className
      )}
    >
      {children}
    </span>
  );
}
```

### Badge Usage

```tsx
<Badge>Default</Badge>
<Badge variant="brand">Brand</Badge>
<Badge variant="success">Success</Badge>
<Badge variant="warning">Warning</Badge>
<Badge variant="error">Error</Badge>
<Badge variant="info">Info</Badge>

// With dot indicator
<Badge variant="success">
  <span className="w-1.5 h-1.5 rounded-full bg-current mr-1.5" />
  Active
</Badge>
```

---

## Dialog

```tsx
import { motion, AnimatePresence } from 'framer-motion';
import { clsx } from 'clsx';
import { type ReactNode, useEffect, useRef } from 'react';
import { XMarkIcon } from '@heroicons/react/24/outline';

interface DialogProps {
  open: boolean;
  onClose: () => void;
  children: ReactNode;
  title?: string;
  description?: string;
  size?: 'sm' | 'md' | 'lg' | 'xl';
}

const sizes = {
  sm: 'max-w-sm',
  md: 'max-w-md',
  lg: 'max-w-lg',
  xl: 'max-w-xl',
};

export function Dialog({ open, onClose, children, title, description, size = 'md' }: DialogProps) {
  const overlayRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const handleEscape = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose();
    };

    if (open) {
      document.addEventListener('keydown', handleEscape);
      document.body.style.overflow = 'hidden';
    }

    return () => {
      document.removeEventListener('keydown', handleEscape);
      document.body.style.overflow = '';
    };
  }, [open, onClose]);

  return (
    <AnimatePresence>
      {open && (
        <div className="fixed inset-0 z-50 flex items-center justify-center">
          {/* Backdrop */}
          <motion.div
            ref={overlayRef}
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: 0.2 }}
            className="absolute inset-0 glass-overlay"
            onClick={(e) => e.target === overlayRef.current && onClose()}
          />

          {/* Dialog */}
          <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={{ duration: 0.2, ease: [0, 0, 0.2, 1] }}
            className={clsx(
              'relative w-full mx-4 glass-card p-6',
              sizes[size]
            )}
          >
            {/* Close button */}
            <button
              onClick={onClose}
              className="absolute top-4 right-4 text-text-muted hover:text-text-primary transition-colors"
            >
              <XMarkIcon className="w-5 h-5" />
            </button>

            {/* Header */}
            {(title || description) && (
              <div className="mb-4 pr-8">
                {title && <h2 className="text-lg font-semibold text-text-primary">{title}</h2>}
                {description && <p className="text-sm text-text-secondary mt-1">{description}</p>}
              </div>
            )}

            {/* Content */}
            {children}
          </motion.div>
        </div>
      )}
    </AnimatePresence>
  );
}

// Subcomponents
export function DialogFooter({ children, className }: { children: ReactNode; className?: string }) {
  return (
    <div className={clsx('mt-6 flex items-center justify-end gap-3', className)}>
      {children}
    </div>
  );
}
```

### Dialog Usage

```tsx
const [open, setOpen] = useState(false);

<Button onClick={() => setOpen(true)}>Open Dialog</Button>

<Dialog
  open={open}
  onClose={() => setOpen(false)}
  title="Confirm Action"
  description="Are you sure you want to continue?"
>
  <p className="text-sm text-text-secondary">
    This action cannot be undone.
  </p>
  <DialogFooter>
    <Button variant="secondary" onClick={() => setOpen(false)}>
      Cancel
    </Button>
    <Button variant="danger" onClick={handleConfirm}>
      Delete
    </Button>
  </DialogFooter>
</Dialog>
```

---

## Tabs

```tsx
import { motion } from 'framer-motion';
import { clsx } from 'clsx';
import { createContext, useContext, useState, type ReactNode } from 'react';

interface TabsContextValue {
  activeTab: string;
  setActiveTab: (tab: string) => void;
}

const TabsContext = createContext<TabsContextValue | null>(null);

interface TabsProps {
  defaultValue: string;
  children: ReactNode;
  className?: string;
}

export function Tabs({ defaultValue, children, className }: TabsProps) {
  const [activeTab, setActiveTab] = useState(defaultValue);

  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className={className}>{children}</div>
    </TabsContext.Provider>
  );
}

export function TabsList({ children, className }: { children: ReactNode; className?: string }) {
  return (
    <div
      className={clsx(
        'flex items-center gap-1 p-1 bg-neutral-bg2 rounded-lg',
        className
      )}
    >
      {children}
    </div>
  );
}

interface TabsTriggerProps {
  value: string;
  children: ReactNode;
  className?: string;
}

export function TabsTrigger({ value, children, className }: TabsTriggerProps) {
  const context = useContext(TabsContext);
  if (!context) throw new Error('TabsTrigger must be used within Tabs');

  const { activeTab, setActiveTab } = context;
  const isActive = activeTab === value;

  return (
    <button
      onClick={() => setActiveTab(value)}
      className={clsx(
        'relative px-3 py-1.5 text-sm font-medium rounded-md transition-colors duration-150',
        isActive ? 'text-text-primary' : 'text-text-muted hover:text-text-secondary',
        className
      )}
    >
      {isActive && (
        <motion.div
          layoutId="activeTab"
          className="absolute inset-0 bg-neutral-bg4 rounded-md"
          transition={{ type: 'spring', stiffness: 500, damping: 30 }}
        />
      )}
      <span className="relative z-10">{children}</span>
    </button>
  );
}

interface TabsContentProps {
  value: string;
  children: ReactNode;
  className?: string;
}

export function TabsContent({ value, children, className }: TabsContentProps) {
  const context = useContext(TabsContext);
  if (!context) throw new Error('TabsContent must be used within Tabs');

  if (context.activeTab !== value) return null;

  return (
    <motion.div
      initial={{ opacity: 0, y: 10 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.2 }}
      className={clsx('mt-4', className)}
    >
      {children}
    </motion.div>
  );
}
```

### Tabs Usage

```tsx
<Tabs defaultValue="overview">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="analytics">Analytics</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="overview">
    <p>Overview content</p>
  </TabsContent>
  <TabsContent value="analytics">
    <p>Analytics content</p>
  </TabsContent>
  <TabsContent value="settings">
    <p>Settings content</p>
  </TabsContent>
</Tabs>
```

---

## Avatar

```tsx
import { clsx } from 'clsx';
import { useState, type ImgHTMLAttributes } from 'react';

type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';

interface AvatarProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'size'> {
  size?: AvatarSize;
  fallback?: string;
}

const sizes: Record<AvatarSize, string> = {
  xs: 'w-6 h-6 text-xs',
  sm: 'w-8 h-8 text-sm',
  md: 'w-10 h-10 text-base',
  lg: 'w-12 h-12 text-lg',
  xl: 'w-16 h-16 text-xl',
};

export function Avatar({ size = 'md', fallback, src, alt, className, ...props }: AvatarProps) {
  const [hasError, setHasError] = useState(false);

  const initials = fallback || alt?.split(' ').map(n => n[0]).join('').slice(0, 2).toUpperCase() || '?';

  if (!src || hasError) {
    return (
      <div
        className={clsx(
          'flex items-center justify-center rounded-full bg-brand-subtle text-brand font-medium',
          sizes[size],
          className
        )}
      >
        {initials}
      </div>
    );
  }

  return (
    <img
      src={src}
      alt={alt}
      onError={() => setHasError(true)}
      className={clsx('rounded-full object-cover', sizes[size], className)}
      {...props}
    />
  );
}
```

---

## Checkbox

```tsx
import { motion } from 'framer-motion';
import { clsx } from 'clsx';
import { forwardRef, type InputHTMLAttributes } from 'react';
import { CheckIcon } from '@heroicons/react/20/solid';

interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
  label?: string;
}

export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
  ({ label, className, id, ...props }, ref) => {
    const checkboxId = id || label?.toLowerCase().replace(/\s+/g, '-');

    return (
      <label className={clsx('flex items-center gap-2 cursor-pointer', className)}>
        <div className="relative">
          <input
            ref={ref}
            type="checkbox"
            id={checkboxId}
            className="peer sr-only"
            {...props}
          />
          <div
            className={clsx(
              'w-5 h-5 rounded border-2 transition-colors duration-150',
              'border-border bg-transparent',
              'peer-checked:border-brand peer-checked:bg-brand',
              'peer-focus-visible:ring-2 peer-focus-visible:ring-brand peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-neutral-bg1',
              'peer-disabled:opacity-50 peer-disabled:cursor-not-allowed'
            )}
          />
          <motion.div
            initial={false}
            animate={{ scale: props.checked ? 1 : 0 }}
            className="absolute inset-0 flex item
...<truncated>
````


### `references/design-tokens.md`

````markdown
# Design Tokens

Complete design token reference for the frontend-ui-dark-ts theme system.

## CSS Custom Properties

Define these in your `globals.css` or a dedicated `tokens.css` file:

```css
:root {
  /* ========================================
   * BRAND COLORS
   * ======================================== */
  --color-brand: #8251EE;
  --color-brand-hover: #9366F5;
  --color-brand-light: #A37EF5;
  --color-brand-subtle: rgba(130, 81, 238, 0.15);
  --color-brand-muted: rgba(130, 81, 238, 0.4);

  /* ========================================
   * NEUTRAL BACKGROUNDS (HSL-based)
   * ======================================== */
  --color-bg-1: hsl(240, 6%, 10%);   /* #18181B - Base/page background */
  --color-bg-2: hsl(240, 5%, 12%);   /* #1F1F23 - Card background */
  --color-bg-3: hsl(240, 5%, 14%);   /* #242428 - Elevated surface */
  --color-bg-4: hsl(240, 4%, 18%);   /* #2D2D32 - Higher elevation */
  --color-bg-5: hsl(240, 4%, 22%);   /* #38383E - Hover states */
  --color-bg-6: hsl(240, 4%, 26%);   /* #43434A - Active states */

  /* ========================================
   * TEXT COLORS
   * ======================================== */
  --color-text-primary: #FFFFFF;
  --color-text-secondary: #A1A1AA;
  --color-text-muted: #71717A;
  --color-text-disabled: #52525B;
  --color-text-inverse: #18181B;

  /* ========================================
   * BORDER COLORS
   * ======================================== */
  --color-border-subtle: hsla(0, 0%, 100%, 0.08);
  --color-border-default: hsla(0, 0%, 100%, 0.12);
  --color-border-strong: hsla(0, 0%, 100%, 0.20);
  --color-border-focus: var(--color-brand);

  /* ========================================
   * STATUS COLORS
   * ======================================== */
  --color-success: #10B981;
  --color-success-subtle: rgba(16, 185, 129, 0.15);
  --color-warning: #F59E0B;
  --color-warning-subtle: rgba(245, 158, 11, 0.15);
  --color-error: #EF4444;
  --color-error-subtle: rgba(239, 68, 68, 0.15);
  --color-info: #3B82F6;
  --color-info-subtle: rgba(59, 130, 246, 0.15);

  /* ========================================
   * DATA VISUALIZATION PALETTE
   * ======================================== */
  --color-dataviz-1: #8251EE;  /* Purple (brand) */
  --color-dataviz-2: #3B82F6;  /* Blue */
  --color-dataviz-3: #10B981;  /* Green */
  --color-dataviz-4: #F59E0B;  /* Yellow */
  --color-dataviz-5: #EF4444;  /* Red */
  --color-dataviz-6: #EC4899;  /* Pink */
  --color-dataviz-7: #06B6D4;  /* Cyan */

  /* ========================================
   * GLASS EFFECT COLORS
   * ======================================== */
  --glass-bg: rgba(255, 255, 255, 0.05);
  --glass-bg-hover: rgba(255, 255, 255, 0.08);
  --glass-border: rgba(255, 255, 255, 0.10);
  --glass-border-strong: rgba(255, 255, 255, 0.15);
  --glass-overlay: rgba(0, 0, 0, 0.60);
  --glass-panel: rgba(0, 0, 0, 0.40);

  /* ========================================
   * SHADOWS
   * ======================================== */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.3);
  --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.3);
  --shadow-glow: 0 0 20px rgba(130, 81, 238, 0.3);
  --shadow-glow-lg: 0 0 40px rgba(130, 81, 238, 0.4);

  /* ========================================
   * SPACING SCALE
   * ======================================== */
  --spacing-0: 0;
  --spacing-px: 1px;
  --spacing-0-5: 0.125rem;  /* 2px */
  --spacing-1: 0.25rem;     /* 4px */
  --spacing-1-5: 0.375rem;  /* 6px */
  --spacing-2: 0.5rem;      /* 8px */
  --spacing-2-5: 0.625rem;  /* 10px */
  --spacing-3: 0.75rem;     /* 12px */
  --spacing-3-5: 0.875rem;  /* 14px */
  --spacing-4: 1rem;        /* 16px */
  --spacing-5: 1.25rem;     /* 20px */
  --spacing-6: 1.5rem;      /* 24px */
  --spacing-7: 1.75rem;     /* 28px */
  --spacing-8: 2rem;        /* 32px */
  --spacing-9: 2.25rem;     /* 36px */
  --spacing-10: 2.5rem;     /* 40px */
  --spacing-12: 3rem;       /* 48px */
  --spacing-14: 3.5rem;     /* 56px */
  --spacing-16: 4rem;       /* 64px */
  --spacing-20: 5rem;       /* 80px */
  --spacing-24: 6rem;       /* 96px */

  /* ========================================
   * BORDER RADIUS
   * ======================================== */
  --radius-none: 0;
  --radius-sm: 0.25rem;     /* 4px */
  --radius-default: 0.375rem; /* 6px */
  --radius-md: 0.5rem;      /* 8px */
  --radius-lg: 0.75rem;     /* 12px */
  --radius-xl: 1rem;        /* 16px */
  --radius-2xl: 1.5rem;     /* 24px */
  --radius-full: 9999px;

  /* ========================================
   * TYPOGRAPHY
   * ======================================== */
  --font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
  --font-mono: 'Cascadia Code', 'Fira Code', monospace;

  /* Font sizes */
  --text-xs: 0.75rem;       /* 12px */
  --text-sm: 0.875rem;      /* 14px */
  --text-base: 1rem;        /* 16px */
  --text-lg: 1.125rem;      /* 18px */
  --text-xl: 1.25rem;       /* 20px */
  --text-2xl: 1.5rem;       /* 24px */
  --text-3xl: 1.875rem;     /* 30px */
  --text-4xl: 2.25rem;      /* 36px */

  /* Line heights */
  --leading-none: 1;
  --leading-tight: 1.25;
  --leading-snug: 1.375;
  --leading-normal: 1.5;
  --leading-relaxed: 1.625;
  --leading-loose: 2;

  /* Font weights */
  --font-normal: 400;
  --font-medium: 500;
  --font-semibold: 600;
  --font-bold: 700;

  /* ========================================
   * TRANSITIONS
   * ======================================== */
  --transition-fast: 150ms ease;
  --transition-normal: 200ms ease;
  --transition-slow: 300ms ease;
  --transition-spring: 200ms cubic-bezier(0.34, 1.56, 0.64, 1);

  /* ========================================
   * Z-INDEX SCALE
   * ======================================== */
  --z-dropdown: 50;
  --z-sticky: 100;
  --z-modal: 200;
  --z-popover: 300;
  --z-tooltip: 400;
  --z-toast: 500;
}
```

## Tailwind Configuration

Extend these tokens in `tailwind.config.js`:

```js
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {
      fontFamily: {
        sans: ['Segoe UI', 'system-ui', '-apple-system', 'sans-serif'],
        mono: ['Cascadia Code', 'Fira Code', 'monospace'],
      },
      colors: {
        brand: {
          DEFAULT: '#8251EE',
          hover: '#9366F5',
          light: '#A37EF5',
          subtle: 'rgba(130, 81, 238, 0.15)',
          muted: 'rgba(130, 81, 238, 0.4)',
        },
        neutral: {
          bg1: 'hsl(240, 6%, 10%)',
          bg2: 'hsl(240, 5%, 12%)',
          bg3: 'hsl(240, 5%, 14%)',
          bg4: 'hsl(240, 4%, 18%)',
          bg5: 'hsl(240, 4%, 22%)',
          bg6: 'hsl(240, 4%, 26%)',
        },
        text: {
          primary: '#FFFFFF',
          secondary: '#A1A1AA',
          muted: '#71717A',
          disabled: '#52525B',
          inverse: '#18181B',
        },
        border: {
          subtle: 'hsla(0, 0%, 100%, 0.08)',
          DEFAULT: 'hsla(0, 0%, 100%, 0.12)',
          strong: 'hsla(0, 0%, 100%, 0.20)',
        },
        status: {
          success: '#10B981',
          'success-subtle': 'rgba(16, 185, 129, 0.15)',
          warning: '#F59E0B',
          'warning-subtle': 'rgba(245, 158, 11, 0.15)',
          error: '#EF4444',
          'error-subtle': 'rgba(239, 68, 68, 0.15)',
          info: '#3B82F6',
          'info-subtle': 'rgba(59, 130, 246, 0.15)',
        },
        dataviz: {
          purple: '#8251EE',
          blue: '#3B82F6',
          green: '#10B981',
          yellow: '#F59E0B',
          red: '#EF4444',
          pink: '#EC4899',
          cyan: '#06B6D4',
        },
        glass: {
          bg: 'rgba(255, 255, 255, 0.05)',
          'bg-hover': 'rgba(255, 255, 255, 0.08)',
          border: 'rgba(255, 255, 255, 0.10)',
          'border-strong': 'rgba(255, 255, 255, 0.15)',
          overlay: 'rgba(0, 0, 0, 0.60)',
          panel: 'rgba(0, 0, 0, 0.40)',
        },
      },
      borderRadius: {
        DEFAULT: '0.375rem',
        lg: '0.75rem',
        xl: '1rem',
        '2xl': '1.5rem',
      },
      boxShadow: {
        glow: '0 0 20px rgba(130, 81, 238, 0.3)',
        'glow-lg': '0 0 40px rgba(130, 81, 238, 0.4)',
      },
      backdropBlur: {
        xs: '2px',
      },
      animation: {
        'fade-in': 'fadeIn 0.3s ease-out',
        'slide-up': 'slideUp 0.3s ease-out',
        'slide-down': 'slideDown 0.3s ease-out',
        'scale-in': 'scaleIn 0.2s ease-out',
      },
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
        slideUp: {
          '0%': { opacity: '0', transform: 'translateY(10px)' },
          '100%': { opacity: '1', transform: 'translateY(0)' },
        },
        slideDown: {
          '0%': { opacity: '0', transform: 'translateY(-10px)' },
          '100%': { opacity: '1', transform: 'translateY(0)' },
        },
        scaleIn: {
          '0%': { opacity: '0', transform: 'scale(0.95)' },
          '100%': { opacity: '1', transform: 'scale(1)' },
        },
      },
      transitionTimingFunction: {
        spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
      },
    },
  },
  plugins: [],
};
```

## Glass Effect Utilities

Add these to your `globals.css` under `@layer components`:

```css
@layer components {
  /* Basic glass effect */
  .glass {
    @apply backdrop-blur-md bg-white/5 border border-white/10;
  }

  /* Glass card with rounded corners */
  .glass-card {
    @apply backdrop-blur-md bg-white/5 border border-white/10 rounded-xl;
  }

  /* Glass card on hover */
  .glass-card-hover {
    @apply backdrop-blur-md bg-white/5 border border-white/10 rounded-xl
           hover:bg-white/[0.08] hover:border-white/[0.15]
           transition-colors duration-200;
  }

  /* Darker glass panel (for sidebars, navigation) */
  .glass-panel {
    @apply backdrop-blur-lg bg-black/40 border border-white/5;
  }

  /* Glass overlay for modals */
  .glass-overlay {
    @apply backdrop-blur-sm bg-black/60;
  }

  /* Glass input field */
  .glass-input {
    @apply backdrop-blur-sm bg-white/5 border border-white/10 
           placeholder:text-text-muted
           focus:border-brand focus:bg-white/[0.08] focus:outline-none
           transition-colors duration-200;
  }

  /* Glass button */
  .glass-button {
    @apply backdrop-blur-sm bg-white/5 border border-white/10
           hover:bg-white/[0.08] hover:border-white/[0.15]
           active:bg-white/[0.12]
           transition-colors duration-150;
  }

  /* Elevated glass (stronger blur, more prominent) */
  .glass-elevated {
    @apply backdrop-blur-xl bg-white/[0.08] border border-white/[0.15]
           shadow-lg;
  }
}
```

## Typography Classes

Add these utility classes for consistent typography:

```css
@layer components {
  /* Headings */
  .heading-1 {
    @apply text-3xl font-bold text-text-primary leading-tight;
  }

  .heading-2 {
    @apply text-2xl font-semibold text-text-primary leading-tight;
  }

  .heading-3 {
    @apply text-xl font-semibold text-text-primary leading-snug;
  }

  .heading-4 {
    @apply text-lg font-semibold text-text-primary leading-snug;
  }

  /* Body text */
  .body-large {
    @apply text-base text-text-secondary leading-relaxed;
  }

  .body {
    @apply text-sm text-text-secondary leading-normal;
  }

  .body-small {
    @apply text-xs text-text-muted leading-normal;
  }

  /* Labels */
  .label {
    @apply text-sm font-medium text-text-secondary;
  }

  .label-small {
    @apply text-xs font-medium text-text-muted uppercase tracking-wide;
  }

  /* Captions */
  .caption {
    @apply text-xs text-text-muted;
  }
}
```

## Framer Motion Timing

Recommended easing functions for Framer Motion:

```tsx
// Timing presets
export const timing = {
  fast: 0.15,
  normal: 0.2,
  slow: 0.3,
  verySlow: 0.5,
};

// Easing presets
export const easing = {
  // Standard ease for most animations
  default: [0.25, 0.1, 0.25, 1],
  
  // Ease out for entering elements
  easeOut: [0, 0, 0.2, 1],
  
  // Ease in for exiting elements
  easeIn: [0.4, 0, 1, 1],
  
  // Spring-like bounce
  spring: [0.34, 1.56, 0.64, 1],
  
  // Smooth deceleration
  decelerate: [0, 0.7, 0.3, 1],
};

// Transition presets
export const transitions = {
  fast: { duration: timing.fast, ease: easing.default },
  normal: { duration: timing.normal, ease: easing.default },
  slow: { duration: timing.slow, ease: easing.easeOut },
  spring: { type: 'spring', stiffness: 400, damping: 17 },
  springGentle: { type: 'spring', stiffness: 200, damping: 20 },
};
```

## Color Palette Summary

### Brand Colors
| Name | Hex | Usage |
|------|-----|-------|
| Brand | `#8251EE` | Primary actions, links, focus rings |
| Brand Hover | `#9366F5` | Hover state for brand elements |
| Brand Light | `#A37EF5` | Lighter accent, selected states |
| Brand Subtle | `rgba(130,81,238,0.15)` | Background for brand badges, tags |

### Neutral Backgrounds
| Name | HSL | Hex | Usage |
|------|-----|-----|-------|
| bg1 | `hsl(240,6%,10%)` | `#18181B` | Page background |
| bg2 | `hsl(240,5%,12%)` | `#1F1F23` | Card background |
| bg3 | `hsl(240,5%,14%)` | `#242428` | Elevated surface, input bg |
| bg4 | `hsl(240,4%,18%)` | `#2D2D32` | Higher elevation |
| bg5 | `hsl(240,4%,22%)` | `#38383E` | Hover states |
| bg6 | `hsl(240,4%,26%)` | `#43434A` | Active/pressed states |

### Text Colors
| Name | Hex | Usage |
|------|-----|-------|
| Primary | `#FFFFFF` | Headings, important text |
| Secondary | `#A1A1AA` | Body text, descriptions |
| Muted | `#71717A` | Captions, placeholders |
| Disabled | `#52525B` | Disabled text |

### Status Colors
| Status | Color | Subtle |
|--------|-------|--------|
| Success | `#10B981` | `rgba(16,185,129,0.15)` |
| Warning | `#F59E0B` | `rgba(245,158,11,0.15)` |
| Error | `#EF4444` | `rgba(239,68,68,0.15)` |
| Info | `#3B82F6` | `rgba(59,130,246,0.15)` |

### Data Visualization
| Index | Color | Name |
|-------|-------|------|
| 1 | `#8251EE` | Purple |
| 2 | `#3B82F6` | Blue |
| 3 | `#10B981` | Green |
| 4 | `#F59E0B` | Yellow |
| 5 | `#EF4444` | Red |
| 6 | `#EC4899` | Pink |
| 7 | `#06B6D4` | Cyan |

````


### `references/patterns.md`

````markdown
# Patterns

Page layouts, navigation patterns, and application templates for dark-themed React applications.

## App Shell

The main application layout with sidebar navigation:

```tsx
import { motion } from 'framer-motion';
import { type ReactNode } from 'react';
import { Sidebar } from './Sidebar';

interface AppShellProps {
  children: ReactNode;
}

export function AppShell({ children }: AppShellProps) {
  return (
    <div className="flex min-h-screen bg-neutral-bg1">
      <Sidebar />
      <main className="flex-1 ml-64">
        {children}
      </main>
    </div>
  );
}
```

---

## Responsive App Shell

Mobile-responsive layout: desktop shows fixed sidebar, mobile shows hamburger menu with slide-in drawer.

**Breakpoint:** `lg` (1024px) — sidebar visible on desktop, hidden on mobile.

```tsx
import { motion, AnimatePresence } from 'framer-motion';
import { type ReactNode, useState } from 'react';
import { Sidebar } from './Sidebar';
import { MobileHeader } from './MobileHeader';
import { MobileDrawer } from './MobileDrawer';

interface ResponsiveAppShellProps {
  children: ReactNode;
}

export function ResponsiveAppShell({ children }: ResponsiveAppShellProps) {
  const [drawerOpen, setDrawerOpen] = useState(false);

  return (
    <div className="flex min-h-screen bg-neutral-bg1">
      {/* Desktop sidebar - hidden on mobile */}
      <div className="hidden lg:block">
        <Sidebar />
      </div>

      {/* Mobile header - visible only on mobile */}
      <MobileHeader onMenuClick={() => setDrawerOpen(true)} />

      {/* Mobile drawer */}
      <MobileDrawer open={drawerOpen} onClose={() => setDrawerOpen(false)} />

      {/* Main content - adjusted margins for desktop sidebar and mobile header */}
      <main className="flex-1 lg:ml-64 pt-14 lg:pt-0 pb-safe-bottom">
        {children}
      </main>
    </div>
  );
}
```

---

## Mobile Header

Fixed top header for mobile with hamburger menu toggle. Uses 44px minimum touch target.

```tsx
import { Bars3Icon } from '@heroicons/react/24/outline';

interface MobileHeaderProps {
  onMenuClick: () => void;
}

export function MobileHeader({ onMenuClick }: MobileHeaderProps) {
  return (
    <header className="fixed top-0 left-0 right-0 h-14 glass-panel flex items-center justify-between px-4 lg:hidden z-40">
      {/* Logo */}
      <div className="flex items-center gap-2">
        <img src="/logo.svg" alt="Logo" className="w-6 h-6" />
        <span className="text-base font-semibold text-text-primary">AppName</span>
      </div>

      {/* Hamburger menu - 44px touch target */}
      <button
        onClick={onMenuClick}
        className="min-w-touch min-h-touch flex items-center justify-center rounded-lg hover:bg-white/5 active:bg-white/10 transition-colors"
        aria-label="Open menu"
      >
        <Bars3Icon className="w-6 h-6 text-text-primary" />
      </button>
    </header>
  );
}
```

---

## Mobile Drawer

Animated slide-in navigation drawer for mobile. Includes backdrop and close button.

```tsx
import { motion, AnimatePresence } from 'framer-motion';
import { XMarkIcon } from '@heroicons/react/24/outline';
import { NavLink, useLocation } from 'react-router-dom';
import { clsx } from 'clsx';
import {
  HomeIcon,
  ChartBarIcon,
  Cog6ToothIcon,
  UsersIcon,
  FolderIcon,
} from '@heroicons/react/24/outline';

const navItems = [
  { path: '/', label: 'Dashboard', icon: HomeIcon },
  { path: '/analytics', label: 'Analytics', icon: ChartBarIcon },
  { path: '/projects', label: 'Projects', icon: FolderIcon },
  { path: '/team', label: 'Team', icon: UsersIcon },
  { path: '/settings', label: 'Settings', icon: Cog6ToothIcon },
];

interface MobileDrawerProps {
  open: boolean;
  onClose: () => void;
}

export function MobileDrawer({ open, onClose }: MobileDrawerProps) {
  const location = useLocation();

  return (
    <AnimatePresence>
      {open && (
        <>
          {/* Backdrop */}
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: 0.2 }}
            className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 lg:hidden"
            onClick={onClose}
          />

          {/* Drawer */}
          <motion.aside
            initial={{ x: '-100%' }}
            animate={{ x: 0 }}
            exit={{ x: '-100%' }}
            transition={{ type: 'spring', stiffness: 400, damping: 30 }}
            className="fixed left-0 top-0 bottom-0 w-72 glass-panel p-4 flex flex-col z-50 lg:hidden pl-safe-left"
          >
            {/* Header with close button */}
            <div className="flex items-center justify-between mb-6">
              <div className="flex items-center gap-2">
                <img src="/logo.svg" alt="Logo" className="w-8 h-8" />
                <span className="text-lg font-semibold text-text-primary">AppName</span>
              </div>
              <button
                onClick={onClose}
                className="min-w-touch min-h-touch flex items-center justify-center rounded-lg hover:bg-white/5 active:bg-white/10 transition-colors"
                aria-label="Close menu"
              >
                <XMarkIcon className="w-6 h-6 text-text-primary" />
              </button>
            </div>

            {/* Navigation */}
            <nav className="flex-1 space-y-1">
              {navItems.map((item) => {
                const isActive = location.pathname === item.path;
                const Icon = item.icon;

                return (
                  <NavLink key={item.path} to={item.path} onClick={onClose}>
                    <motion.div
                      className={clsx(
                        'relative flex items-center gap-3 px-3 py-3 rounded-lg',
                        'text-base font-medium transition-colors duration-150',
                        'min-h-touch', // 44px touch target
                        isActive
                          ? 'text-text-primary bg-brand/20 border border-brand/30'
                          : 'text-text-muted hover:text-text-secondary hover:bg-white/5 active:bg-white/10'
                      )}
                      whileTap={{ scale: 0.98 }}
                    >
                      <Icon className={clsx('w-5 h-5', isActive && 'text-brand')} />
                      <span>{item.label}</span>
                    </motion.div>
                  </NavLink>
                );
              })}
            </nav>

            {/* User profile at bottom */}
            <div className="mt-auto pt-4 border-t border-white/10 pb-safe-bottom">
              <div className="flex items-center gap-3 px-3 py-2">
                <div className="w-10 h-10 rounded-full bg-brand-subtle flex items-center justify-center">
                  <span className="text-sm font-medium text-brand">JD</span>
                </div>
                <div className="flex-1 min-w-0">
                  <p className="text-sm font-medium text-text-primary truncate">John Doe</p>
                  <p className="text-xs text-text-muted truncate">john@example.com</p>
                </div>
              </div>
            </div>
          </motion.aside>
        </>
      )}
    </AnimatePresence>
  );
}
```

---

## Sidebar Navigation

Glass-effect sidebar with animated navigation items:

```tsx
import { motion } from 'framer-motion';
import { NavLink, useLocation } from 'react-router-dom';
import { clsx } from 'clsx';
import {
  HomeIcon,
  ChartBarIcon,
  Cog6ToothIcon,
  UsersIcon,
  FolderIcon,
} from '@heroicons/react/24/outline';

const navItems = [
  { path: '/', label: 'Dashboard', icon: HomeIcon },
  { path: '/analytics', label: 'Analytics', icon: ChartBarIcon },
  { path: '/projects', label: 'Projects', icon: FolderIcon },
  { path: '/team', label: 'Team', icon: UsersIcon },
  { path: '/settings', label: 'Settings', icon: Cog6ToothIcon },
];

export function Sidebar() {
  const location = useLocation();

  return (
    <aside className="fixed left-0 top-0 h-screen w-64 glass-panel p-4 flex flex-col">
      {/* Logo */}
      <div className="flex items-center gap-3 px-3 py-4 mb-6">
        <img src="/logo.svg" alt="Logo" className="w-8 h-8" />
        <span className="text-lg font-semibold text-text-primary">AppName</span>
      </div>

      {/* Navigation */}
      <nav className="flex-1 space-y-1">
        {navItems.map((item) => {
          const isActive = location.pathname === item.path;
          const Icon = item.icon;

          return (
            <NavLink key={item.path} to={item.path}>
              <motion.div
                className={clsx(
                  'relative flex items-center gap-3 px-3 py-2.5 rounded-lg',
                  'text-sm font-medium transition-colors duration-150',
                  isActive
                    ? 'text-text-primary'
                    : 'text-text-muted hover:text-text-secondary hover:bg-white/5'
                )}
                whileHover={{ x: 4 }}
                transition={{ type: 'spring', stiffness: 400, damping: 25 }}
              >
                {isActive && (
                  <motion.div
                    layoutId="activeNavItem"
                    className="absolute inset-0 bg-brand/20 border border-brand/30 rounded-lg"
                    transition={{ type: 'spring', stiffness: 500, damping: 30 }}
                  />
                )}
                <Icon className={clsx('w-5 h-5 relative z-10', isActive && 'text-brand')} />
                <span className="relative z-10">{item.label}</span>
              </motion.div>
            </NavLink>
          );
        })}
      </nav>

      {/* User profile at bottom */}
      <div className="mt-auto pt-4 border-t border-white/10">
        <div className="flex items-center gap-3 px-3 py-2">
          <div className="w-8 h-8 rounded-full bg-brand-subtle flex items-center justify-center">
            <span className="text-sm font-medium text-brand">JD</span>
          </div>
          <div className="flex-1 min-w-0">
            <p className="text-sm font-medium text-text-primary truncate">John Doe</p>
            <p className="text-xs text-text-muted truncate">john@example.com</p>
          </div>
        </div>
      </div>
    </aside>
  );
}
```

---

## Page Header

Reusable header component for pages:

```tsx
import { motion } from 'framer-motion';
import { type ReactNode } from 'react';

interface PageHeaderProps {
  title: string;
  description?: string;
  actions?: ReactNode;
}

export function PageHeader({ title, description, actions }: PageHeaderProps) {
  return (
    <motion.header
      initial={{ opacity: 0, y: -10 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.3 }}
      className="flex items-center justify-between mb-8"
    >
      <div>
        <h1 className="text-2xl font-semibold text-text-primary">{title}</h1>
        {description && (
          <p className="text-sm text-text-secondary mt-1">{description}</p>
        )}
      </div>
      {actions && <div className="flex items-center gap-3">{actions}</div>}
    </motion.header>
  );
}
```

---

## Page Transition Wrapper

Animate page transitions with route changes:

```tsx
import { motion } from 'framer-motion';
import { type ReactNode } from 'react';

interface PageTransitionProps {
  children: ReactNode;
}

const pageVariants = {
  initial: { opacity: 0, y: 20 },
  animate: { opacity: 1, y: 0 },
  exit: { opacity: 0, y: -20 },
};

export function PageTransition({ children }: PageTransitionProps) {
  return (
    <motion.div
      variants={pageVariants}
      initial="initial"
      animate="animate"
      exit="exit"
      transition={{ duration: 0.3, ease: 'easeOut' }}
    >
      {children}
    </motion.div>
  );
}
```

### Usage with React Router

```tsx
import { Routes, Route, useLocation } from 'react-router-dom';
import { AnimatePresence } from 'framer-motion';

function App() {
  const location = useLocation();

  return (
    <AppShell>
      <AnimatePresence mode="wait">
        <Routes location={location} key={location.pathname}>
          <Route path="/" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </AnimatePresence>
    </AppShell>
  );
}
```

---

## Basic Page Template

Standard page layout with header and content area:

```tsx
import { PageTransition } from '../components/layout/PageTransition';
import { PageHeader } from '../components/layout/PageHeader';
import { Button } from '../components/ui';

export function BasicPage() {
  return (
    <PageTransition>
      <div className="p-8">
        <PageHeader
          title="Page Title"
          description="A brief description of this page"
          actions={
            <Button>Primary Action</Button>
          }
        />

        <div className="space-y-6">
          {/* Page content */}
        </div>
      </div>
    </PageTransition>
  );
}
```

---

## List Page Template

Page with search, filters, and data table:

```tsx
import { useState } from 'react';
import { motion } from 'framer-motion';
import { PageTransition } from '../components/layout/PageTransition';
import { PageHeader } from '../components/layout/PageHeader';
import { Button, SearchInput, Card, Badge } from '../components/ui';
import { PlusIcon } from '@heroicons/react/20/solid';

interface Item {
  id: string;
  name: string;
  status: 'active' | 'inactive' | 'pending';
  createdAt: string;
}

const staggerContainer = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: { staggerChildren: 0.05 },
  },
};

const staggerItem = {
  hidden: { opacity: 0, y: 10 },
  visible: { opacity: 1, y: 0 },
};

export function ListPage() {
  const [search, setSearch] = useState('');
  const [items] = useState<Item[]>([
    { id: '1', name: 'Project Alpha', status: 'active', createdAt: '2024-01-15' },
    { id: '2', name: 'Project Beta', status: 'pending', createdAt: '2024-01-14' },
    { id: '3', name: 'Project Gamma', status: 'inactive', createdAt: '2024-01-13' },
  ]);

  const filteredItems = items.filter((item) =>
    item.name.toLowerCase().includes(search.toLowerCase())
  );

  return (
    <PageTransition>
      <div className="p-8">
        <PageHeader
          title="Projects"
          description="Manage your projects and their settings"
          actions={
            <Button leftIcon={<PlusIcon className="w-4 h-4" />}>
              New Project
            </Button>
          }
        />

        {/* Filters */}
        <div className="flex items-center gap-4 mb-6">
          <div className="w-72">
            <SearchInput
              value={search}
              onChange={setSearch}
              placeholder="Search projects..."
            />
          </div>
        </div>

        {/* Table */}
        <Card padding="none">
          <table className="w-full">
            <thead>
              <tr className="border-b border-border">
                <th className="text-left text-xs font-medium text-text-muted uppercase tracking-wide px-4 py-3">
                  Name
                </th>
                <th className="text-left text-xs font-medium text-text-muted uppercase tracking-wide px-4 py-3">
                  Status
                </th>
                <th className="text-left text-xs font-medium text-text-muted uppercase tracking-wide px-4 py-3">
                  Created
                </th>
              </tr>
            </thead>
            <motion.tbody
              variants={staggerContainer}
              initial="hidden"
              animate="visible"
            >
              {filteredItems.map((item) => (
                <motion.tr
                  key={item.id}
                  variants={staggerItem}
                  className="border-b border-border last:border-0 hover:bg-neutral-bg3 transition-colors cursor-pointer"
                >
                  <td className="px-4 py-3">
                    <span className="text-sm font-medium text-text-primary">
                      {item.name}
                    </span>
                  </td>
                  <td className="px-4 py-3">
                    <Badge
                      variant={
                        item.status === 'active' ? 'success' :
                        item.status === 'pending' ? 'warning' : 'default'
                      }
                    >
                      {item.status}
                    </Badge>
                  </td>
                  <td className="px-4 py-3">
                    <span className="text-sm text-text-secondary">
                      {item.createdAt}
                    </span>
                  </td>
                </motion.tr>
              ))}
            </motion.tbody>
          </table>
        </Card>
      </div>
    </PageTransition>
  );
}
```

---

## Tabs Page Template

Page with tab navigation:

```tsx
import { PageTransition } from '../components/layout/PageTransition';
import { PageHeader } from '../components/layout/PageHeader';
import { Tabs, TabsList, TabsTrigger, TabsContent, Card } from '../components/ui';

export function TabsPage() {
  return (
    <PageTransition>
      <div className="p-8">
        <PageHeader
          title="Settings"
          description="Manage your account and preferences"
        />

        <Tabs defaultValue="general">
          <TabsList>
            <TabsTrigger value="general">General</TabsTrigger>
            <TabsTrigger value="security">Security</TabsTrigger>
            <TabsTrigger value="notifications">Notifications</TabsTrigger>
            <TabsTrigger value="integrations">Integrations</TabsTrigger>
          </TabsList>

          <TabsContent value="general">
            <Card>
              <h3 className="text-lg font-semibold text-text-primary mb-4">
                General Settings
              </h3>
              {/* General settings form */}
            </Card>
          </TabsContent>

          <TabsContent value="security">
            <Card>
              <h3 className="text-lg font-semibold text-text-primary mb-4">
                Security Settings
              </h3>
              {/* Security settings form */}
            </Card>
          </TabsContent>

          <TabsContent value="notifications">
            <Card>
              <h3 className="text-lg font-semibold text-text-primary mb-4">
                Notification Preferences
              </h3>
              {/* Notification settings */}
            </Card>
          </TabsContent>

          <TabsContent value="integrations">
            <Card>
              <h3 className="text-lg font-semibold text-text-primary mb-4">
                Connected Integrations
              </h3>
              {/* Integrations list */}
            </Card>
          </TabsContent>
        </Tabs>
      </div>
    </PageTransition>
  );
}
```

---

## Dashboard Template

Dashboard with stat cards and data visualization:

```tsx
import { motion } from 'framer-motion';
import { PageTransition } from '../components/layout/PageTransition';
import { PageHeader } from '../components/layout/PageHeader';
import { Card, CardTitle, CardContent } from '../components/ui';
import {
  ArrowUpIcon,
  ArrowDownIcon,
  UsersIcon,
  CurrencyDollarIcon,
  ChartBarIcon,
  ClockIcon,
} from '@heroicons/react/24/outline';

const stats = [
  { label: 'Total Revenue', value: '$45,231', change: '+12.5%', trend: 'up', icon: CurrencyDollarIcon },
  { label: 'Active Users', value: '2,345', change: '+8.2%', trend: 'up', icon: UsersIcon },
  { label: 'Conversion Rate', value: '3.2%', change: '-2.1%', trend: 'down', icon: ChartBarIcon },
  { label: 'Avg. Session', value: '4m 32s', change: '+18.7%', trend: 'up', icon: ClockIcon },
];

const staggerContainer = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: { staggerChildren: 0.1 },
  },
};

const staggerItem = {
  hidden: { opacity: 0, 
...<truncated>
````
