GitHub Copilot ChatGPT Claude Codex CLI Cursor opencode Skill Text

frontend-ui-dark-ts

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.

Ciza · 0 points · 22 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download microsoft-skills-.github_plugins_azure-sdk-typescript_skills_frontend-ui-dark-ts-e58528d.zip · 165 KB
Part of microsoft/skills — 195 skills

Install

skills CLI npx skills add https://github.com/microsoft/skills/tree/main/.github/plugins/azure-sdk-typescript/skills/frontend-ui-dark-ts
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install microsoft-skills@llmmart
Git git clone https://github.com/microsoft/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole microsoft/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

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

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:

<!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:

{
  "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

/** @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

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

src/styles/globals.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

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

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

// 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

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

<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)

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

Glass Modal Overlay

<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 — Complete color system, spacing, typography scales
  • Components — Button, Card, Input, Dialog, Tabs, and more
  • Patterns — Page layouts, navigation, lists, forms
Files (skills)
  • assets
    • foundry-logo-dark.png 29.3 KB · in bundle
    • foundry-logo-light.png 24.9 KB · in bundle
    • Segoe UI Bold Italic.ttf 41.6 KB · in bundle
    • Segoe UI Bold.ttf 35.2 KB · in bundle
    • Segoe UI Italic.ttf 40.4 KB · in bundle
    • Segoe UI.ttf 33.4 KB · in bundle
  • references
    • components.md 26.1 KB
      # 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 items-center justify-center text-white pointer-events-none"
                >
                  <CheckIcon className="w-3.5 h-3.5" />
                </motion.div>
              </div>
              {label && <span className="text-sm text-text-secondary">{label}</span>}
            </label>
          );
        }
      );
      
      Checkbox.displayName = 'Checkbox';
      ```
      
      ---
      
      ## Select
      
      ```tsx
      import { clsx } from 'clsx';
      import { forwardRef, type SelectHTMLAttributes } from 'react';
      import { ChevronDownIcon } from '@heroicons/react/20/solid';
      
      interface SelectOption {
        value: string;
        label: string;
      }
      
      interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
        label?: string;
        options: SelectOption[];
        error?: string;
        placeholder?: string;
      }
      
      export const Select = forwardRef<HTMLSelectElement, SelectProps>(
        ({ label, options, error, placeholder, className, id, ...props }, ref) => {
          const selectId = id || label?.toLowerCase().replace(/\s+/g, '-');
      
          return (
            <div className="flex flex-col gap-1.5">
              {label && (
                <label htmlFor={selectId} className="text-sm font-medium text-text-secondary">
                  {label}
                </label>
              )}
              <div className="relative">
                <select
                  ref={ref}
                  id={selectId}
                  className={clsx(
                    'w-full h-10 px-3 pr-10 rounded-lg text-sm appearance-none',
                    'bg-neutral-bg2 border border-border text-text-primary',
                    'focus:outline-none focus:border-brand focus:bg-neutral-bg3',
                    'disabled:opacity-50 disabled:cursor-not-allowed',
                    'transition-colors duration-150',
                    error && 'border-status-error focus:border-status-error',
                    className
                  )}
                  {...props}
                >
                  {placeholder && (
                    <option value="" disabled>
                      {placeholder}
                    </option>
                  )}
                  {options.map((option) => (
                    <option key={option.value} value={option.value}>
                      {option.label}
                    </option>
                  ))}
                </select>
                <ChevronDownIcon className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-text-muted pointer-events-none" />
              </div>
              {error && <p className="text-xs text-status-error">{error}</p>}
            </div>
          );
        }
      );
      
      Select.displayName = 'Select';
      ```
      
      ---
      
      ## Toast
      
      ```tsx
      import { motion, AnimatePresence } from 'framer-motion';
      import { clsx } from 'clsx';
      import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
      import { CheckCircleIcon, ExclamationCircleIcon, InformationCircleIcon, XCircleIcon, XMarkIcon } from '@heroicons/react/24/outline';
      
      type ToastVariant = 'success' | 'error' | 'warning' | 'info';
      
      interface Toast {
        id: string;
        variant: ToastVariant;
        title: string;
        description?: string;
      }
      
      interface ToastContextValue {
        toasts: Toast[];
        addToast: (toast: Omit<Toast, 'id'>) => void;
        removeToast: (id: string) => void;
      }
      
      const ToastContext = createContext<ToastContextValue | null>(null);
      
      export function ToastProvider({ children }: { children: ReactNode }) {
        const [toasts, setToasts] = useState<Toast[]>([]);
      
        const addToast = useCallback((toast: Omit<Toast, 'id'>) => {
          const id = Math.random().toString(36).slice(2);
          setToasts((prev) => [...prev, { ...toast, id }]);
      
          // Auto-remove after 5 seconds
          setTimeout(() => {
            setToasts((prev) => prev.filter((t) => t.id !== id));
          }, 5000);
        }, []);
      
        const removeToast = useCallback((id: string) => {
          setToasts((prev) => prev.filter((t) => t.id !== id));
        }, []);
      
        return (
          <ToastContext.Provider value={{ toasts, addToast, removeToast }}>
            {children}
            <ToastContainer />
          </ToastContext.Provider>
        );
      }
      
      export function useToast() {
        const context = useContext(ToastContext);
        if (!context) throw new Error('useToast must be used within ToastProvider');
        return context;
      }
      
      const icons: Record<ToastVariant, typeof CheckCircleIcon> = {
        success: CheckCircleIcon,
        error: XCircleIcon,
        warning: ExclamationCircleIcon,
        info: InformationCircleIcon,
      };
      
      const variants: Record<ToastVariant, string> = {
        success: 'text-status-success',
        error: 'text-status-error',
        warning: 'text-status-warning',
        info: 'text-status-info',
      };
      
      function ToastContainer() {
        const { toasts, removeToast } = useToast();
      
        return (
          <div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
            <AnimatePresence>
              {toasts.map((toast) => {
                const Icon = icons[toast.variant];
                return (
                  <motion.div
                    key={toast.id}
                    initial={{ opacity: 0, y: 20, scale: 0.95 }}
                    animate={{ opacity: 1, y: 0, scale: 1 }}
                    exit={{ opacity: 0, x: 100, scale: 0.95 }}
                    transition={{ duration: 0.2 }}
                    className="glass-card p-4 min-w-[300px] max-w-[400px] flex items-start gap-3"
                  >
                    <Icon className={clsx('w-5 h-5 flex-shrink-0 mt-0.5', variants[toast.variant])} />
                    <div className="flex-1 min-w-0">
                      <p className="text-sm font-medium text-text-primary">{toast.title}</p>
                      {toast.description && (
                        <p className="text-sm text-text-secondary mt-1">{toast.description}</p>
                      )}
                    </div>
                    <button
                      onClick={() => removeToast(toast.id)}
                      className="text-text-muted hover:text-text-primary transition-colors"
                    >
                      <XMarkIcon className="w-4 h-4" />
                    </button>
                  </motion.div>
                );
              })}
            </AnimatePresence>
          </div>
        );
      }
      ```
      
      ### Toast Usage
      
      ```tsx
      // Wrap app with provider
      <ToastProvider>
        <App />
      </ToastProvider>
      
      // Use in components
      const { addToast } = useToast();
      
      addToast({
        variant: 'success',
        title: 'Changes saved',
        description: 'Your settings have been updated.',
      });
      
      addToast({
        variant: 'error',
        title: 'Error',
        description: 'Something went wrong.',
      });
      ```
      
      ---
      
      ## Component Index
      
      Export all components from a central file:
      
      ```tsx
      // src/components/ui/index.ts
      export { Button } from './Button';
      export { Input, SearchInput } from './Input';
      export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './Card';
      export { Badge } from './Badge';
      export { Dialog, DialogFooter } from './Dialog';
      export { Tabs, TabsList, TabsTrigger, TabsContent } from './Tabs';
      export { Avatar } from './Avatar';
      export { Checkbox } from './Checkbox';
      export { Select } from './Select';
      export { ToastProvider, useToast } from './Toast';
      ```
      
    • design-tokens.md 13.9 KB
      # 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 |
      
    • patterns.md 30.1 KB
      # 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, y: 20 },
        visible: { opacity: 1, y: 0 },
      };
      
      export function Dashboard() {
        return (
          <PageTransition>
            <div className="p-8">
              <PageHeader
                title="Dashboard"
                description="Welcome back! Here's what's happening."
              />
      
              {/* Stat Cards */}
              <motion.div
                variants={staggerContainer}
                initial="hidden"
                animate="visible"
                className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8"
              >
                {stats.map((stat) => {
                  const Icon = stat.icon;
                  const TrendIcon = stat.trend === 'up' ? ArrowUpIcon : ArrowDownIcon;
      
                  return (
                    <motion.div key={stat.label} variants={staggerItem}>
                      <Card variant="glass" className="relative overflow-hidden">
                        <div className="flex items-start justify-between">
                          <div>
                            <p className="text-sm text-text-muted">{stat.label}</p>
                            <p className="text-2xl font-semibold text-text-primary mt-1">
                              {stat.value}
                            </p>
                            <div className="flex items-center gap-1 mt-2">
                              <TrendIcon
                                className={`w-4 h-4 ${
                                  stat.trend === 'up' ? 'text-status-success' : 'text-status-error'
                                }`}
                              />
                              <span
                                className={`text-sm ${
                                  stat.trend === 'up' ? 'text-status-success' : 'text-status-error'
                                }`}
                              >
                                {stat.change}
                              </span>
                              <span className="text-xs text-text-muted">vs last month</span>
                            </div>
                          </div>
                          <div className="p-2 rounded-lg bg-brand-subtle">
                            <Icon className="w-5 h-5 text-brand" />
                          </div>
                        </div>
                      </Card>
                    </motion.div>
                  );
                })}
              </motion.div>
      
              {/* Main Content Grid */}
              <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
                {/* Chart Card */}
                <Card variant="glass" className="lg:col-span-2">
                  <CardTitle>Revenue Overview</CardTitle>
                  <CardContent className="h-80">
                    {/* Chart component would go here */}
                    <div className="w-full h-full flex items-center justify-center text-text-muted">
                      Chart placeholder
                    </div>
                  </CardContent>
                </Card>
      
                {/* Recent Activity */}
                <Card variant="glass">
                  <CardTitle>Recent Activity</CardTitle>
                  <CardContent>
                    <ul className="space-y-4">
                      {[1, 2, 3, 4].map((i) => (
                        <li key={i} className="flex items-start gap-3">
                          <div className="w-8 h-8 rounded-full bg-brand-subtle flex items-center justify-center flex-shrink-0">
                            <UsersIcon className="w-4 h-4 text-brand" />
                          </div>
                          <div>
                            <p className="text-sm text-text-primary">New user signed up</p>
                            <p className="text-xs text-text-muted">2 minutes ago</p>
                          </div>
                        </li>
                      ))}
                    </ul>
                  </CardContent>
                </Card>
              </div>
            </div>
          </PageTransition>
        );
      }
      ```
      
      ---
      
      ## Stagger Animation Patterns
      
      Reusable animation variants for list items:
      
      ```tsx
      // variants.ts
      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' },
        },
      };
      
      export const fadeInScale = {
        hidden: { opacity: 0, scale: 0.95 },
        visible: {
          opacity: 1,
          scale: 1,
          transition: { duration: 0.2, ease: 'easeOut' },
        },
      };
      
      export const slideInFromRight = {
        hidden: { opacity: 0, x: 20 },
        visible: {
          opacity: 1,
          x: 0,
          transition: { duration: 0.3, ease: 'easeOut' },
        },
      };
      
      export const slideInFromLeft = {
        hidden: { opacity: 0, x: -20 },
        visible: {
          opacity: 1,
          x: 0,
          transition: { duration: 0.3, ease: 'easeOut' },
        },
      };
      ```
      
      ### Using Stagger Animations
      
      ```tsx
      import { motion } from 'framer-motion';
      import { staggerContainer, staggerItem } from './variants';
      
      function ItemList({ items }) {
        return (
          <motion.ul
            variants={staggerContainer}
            initial="hidden"
            animate="visible"
            className="space-y-2"
          >
            {items.map((item) => (
              <motion.li key={item.id} variants={staggerItem}>
                {item.name}
              </motion.li>
            ))}
          </motion.ul>
        );
      }
      ```
      
      ---
      
      ## Glass Panel Layouts
      
      ### Two-Column Layout with Glass Panels
      
      ```tsx
      export function TwoColumnLayout() {
        return (
          <div className="flex gap-6 p-8">
            {/* Main content */}
            <div className="flex-1">
              <Card variant="glass">
                <h2 className="text-lg font-semibold text-text-primary mb-4">
                  Main Content
                </h2>
                {/* Content */}
              </Card>
            </div>
      
            {/* Sidebar */}
            <aside className="w-80">
              <Card variant="glass" className="sticky top-8">
                <h3 className="text-sm font-medium text-text-muted uppercase tracking-wide mb-4">
                  Quick Actions
                </h3>
                {/* Sidebar content */}
              </Card>
            </aside>
          </div>
        );
      }
      ```
      
      ### Grid of Glass Cards
      
      ```tsx
      export function CardGrid({ items }) {
        return (
          <motion.div
            variants={staggerContainer}
            initial="hidden"
            animate="visible"
            className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
          >
            {items.map((item) => (
              <motion.div key={item.id} variants={staggerItem}>
                <Card variant="glass" interactive>
                  <h3 className="text-base font-medium text-text-primary">
                    {item.title}
                  </h3>
                  <p className="text-sm text-text-secondary mt-2">
                    {item.description}
                  </p>
                </Card>
              </motion.div>
            ))}
          </motion.div>
        );
      }
      ```
      
      ---
      
      ## Form Patterns
      
      ### Settings Form
      
      ```tsx
      import { Button, Input, Select, Checkbox, Card } from '../components/ui';
      
      export function SettingsForm() {
        return (
          <Card>
            <h2 className="text-lg font-semibold text-text-primary mb-6">
              Account Settings
            </h2>
      
            <form className="space-y-6">
              {/* Two-column grid for larger screens */}
              <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
                <Input
                  label="First Name"
                  placeholder="Enter your first name"
                />
                <Input
                  label="Last Name"
                  placeholder="Enter your last name"
                />
              </div>
      
              <Input
                label="Email Address"
                type="email"
                placeholder="you@example.com"
              />
      
              <Select
                label="Timezone"
                placeholder="Select timezone"
                options={[
                  { value: 'utc', label: 'UTC' },
                  { value: 'pst', label: 'Pacific Time (PST)' },
                  { value: 'est', label: 'Eastern Time (EST)' },
                ]}
              />
      
              <div className="border-t border-border pt-6">
                <h3 className="text-sm font-medium text-text-primary mb-4">
                  Notification Preferences
                </h3>
                <div className="space-y-3">
                  <Checkbox label="Email notifications" defaultChecked />
                  <Checkbox label="Push notifications" />
                  <Checkbox label="Weekly digest" defaultChecked />
                </div>
              </div>
      
              <div className="flex justify-end gap-3 pt-4">
                <Button variant="secondary">Cancel</Button>
                <Button>Save Changes</Button>
              </div>
            </form>
          </Card>
        );
      }
      ```
      
      ---
      
      ## Empty States
      
      ```tsx
      import { motion } from 'framer-motion';
      import { FolderIcon } from '@heroicons/react/24/outline';
      import { Button } from '../components/ui';
      
      interface EmptyStateProps {
        icon?: typeof FolderIcon;
        title: string;
        description: string;
        action?: {
          label: string;
          onClick: () => void;
        };
      }
      
      export function EmptyState({ icon: Icon = FolderIcon, title, description, action }: EmptyStateProps) {
        return (
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            className="flex flex-col items-center justify-center py-16 px-4"
          >
            <div className="w-16 h-16 rounded-full bg-neutral-bg3 flex items-center justify-center mb-4">
              <Icon className="w-8 h-8 text-text-muted" />
            </div>
            <h3 className="text-lg font-medium text-text-primary mb-2">{title}</h3>
            <p className="text-sm text-text-secondary text-center max-w-sm mb-6">
              {description}
            </p>
            {action && (
              <Button onClick={action.onClick}>{action.label}</Button>
            )}
          </motion.div>
        );
      }
      ```
      
      ### Usage
      
      ```tsx
      <EmptyState
        title="No projects yet"
        description="Get started by creating your first project."
        action={{
          label: 'Create Project',
          onClick: () => setShowCreateModal(true),
        }}
      />
      ```
      
      ---
      
      ## Loading States
      
      ### Skeleton Loader
      
      ```tsx
      import { clsx } from 'clsx';
      
      interface SkeletonProps {
        className?: string;
      }
      
      export function Skeleton({ className }: SkeletonProps) {
        return (
          <div
            className={clsx(
              'animate-pulse bg-neutral-bg4 rounded',
              className
            )}
          />
        );
      }
      
      // Usage
      <Skeleton className="h-4 w-32" />
      <Skeleton className="h-10 w-full" />
      <Skeleton className="h-40 w-full rounded-xl" />
      ```
      
      ### Card Skeleton
      
      ```tsx
      export function CardSkeleton() {
        return (
          <Card>
            <div className="space-y-4">
              <Skeleton className="h-5 w-1/3" />
              <Skeleton className="h-4 w-full" />
              <Skeleton className="h-4 w-2/3" />
            </div>
          </Card>
        );
      }
      ```
      
      ### Table Skeleton
      
      ```tsx
      export function TableSkeleton({ rows = 5 }: { rows?: number }) {
        return (
          <Card padding="none">
            <div className="divide-y divide-border">
              {Array.from({ length: rows }).map((_, i) => (
                <div key={i} className="flex items-center gap-4 p-4">
                  <Skeleton className="h-8 w-8 rounded-full" />
                  <Skeleton className="h-4 flex-1" />
                  <Skeleton className="h-4 w-20" />
                  <Skeleton className="h-4 w-24" />
                </div>
              ))}
            </div>
          </Card>
        );
      }
      ```
      
  • SKILL.md 14.4 KB
    ---
    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
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related