# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Overview **Pastel UI** is a modern, interactive web application for color manipulation, palette generation, and accessibility analysis. Built with Next.js 16 and Tailwind CSS 4, it runs entirely in the browser using WebAssembly for zero-latency color operations. **Technology Stack:** - **Framework**: Next.js 16 (App Router, React 19, Static Export) - **Styling**: Tailwind CSS 4 (CSS-first configuration) - **Language**: TypeScript (strict mode) - **Color Engine**: `@valknarthing/pastel-wasm` (WebAssembly, 130KB) - **State Management**: - `@tanstack/react-query` (server state) - `zustand` (client state) - **Animation**: `framer-motion` - **Icons**: `lucide-react` - **UI Components**: Custom components + shadcn/ui patterns - **Deployment**: Fully static (2.2MB total) ## Project Structure ``` pastel-ui/ ├── app/ # Next.js 16 App Router │ ├── layout.tsx # Root layout with providers │ ├── page.tsx # Home/main playground │ ├── globals.css # Tailwind CSS imports │ ├── playground/ # Main color manipulation tool │ │ └── page.tsx │ ├── palettes/ # Palette generation tools │ │ ├── page.tsx # Palette dashboard │ │ ├── harmony/page.tsx # Harmony-based palettes │ │ ├── distinct/page.tsx # Distinct colors generator │ │ └── gradient/page.tsx # Gradient creator │ ├── accessibility/ # Accessibility tools │ │ ├── page.tsx # A11y dashboard │ │ ├── contrast/page.tsx # Contrast checker │ │ └── colorblind/page.tsx # Colorblindness simulator │ ├── names/ # Named colors explorer │ │ └── page.tsx │ ├── batch/ # Batch operations │ │ └── page.tsx │ ├── docs/ # Documentation │ │ └── page.tsx ├── components/ │ ├── ui/ # Base UI components (shadcn/ui style) │ │ ├── button.tsx │ │ ├── input.tsx │ │ ├── slider.tsx │ │ ├── tabs.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── command.tsx │ │ ├── toast.tsx │ │ └── ... │ ├── color/ # Color-specific components │ │ ├── ColorPicker.tsx # Main color input │ │ ├── ColorDisplay.tsx # Large preview swatch │ │ ├── ColorInfo.tsx # Detailed color information │ │ ├── FormatConverter.tsx # Format conversion tabs │ │ ├── ColorSwatch.tsx # Small color preview │ │ └── ColorInput.tsx # Text input with validation │ ├── tools/ # Tool-specific components │ │ ├── ManipulationPanel.tsx # Lighten/darken/etc controls │ │ ├── PaletteGenerator.tsx # Palette creation UI │ │ ├── AccessibilityChecker.tsx # Contrast/WCAG analysis │ │ ├── GradientCreator.tsx # Gradient builder │ │ ├── ColorBlindSimulator.tsx # Colorblindness preview │ │ └── DistinctGenerator.tsx # Distinct colors UI │ ├── layout/ # Layout components │ │ ├── Navbar.tsx # Top navigation │ │ ├── Sidebar.tsx # Side navigation │ │ ├── Footer.tsx # Footer │ │ ├── ThemeToggle.tsx # Dark/light mode │ │ └── CommandPalette.tsx # Cmd+K interface │ └── providers/ # Context providers │ ├── Providers.tsx # Root provider wrapper │ ├── ThemeProvider.tsx # Theme context │ └── QueryProvider.tsx # React Query provider ├── lib/ │ ├── api/ # API client │ │ ├── client.ts # PastelAPIClient class │ │ ├── types.ts # API type definitions │ │ ├── endpoints.ts # Endpoint definitions │ │ └── queries.ts # React Query hooks │ ├── utils/ # Utilities │ │ ├── color.ts # Color helpers │ │ ├── format.ts # Format converters │ │ ├── export.ts # Export utilities (CSS, JSON, etc.) │ │ ├── validation.ts # Input validation │ │ └── cn.ts # Class name utility │ ├── hooks/ # Custom React hooks │ │ ├── useColor.ts # Color state management │ │ ├── usePalette.ts # Palette state │ │ ├── useColorHistory.ts # History tracking │ │ ├── useKeyboard.ts # Keyboard shortcuts │ │ └── useClipboard.ts # Copy to clipboard │ ├── stores/ # Zustand stores │ │ ├── colorStore.ts # Color state │ │ ├── historyStore.ts # Color history │ │ └── preferencesStore.ts # User preferences │ └── constants/ # Constants │ ├── colors.ts # Named colors │ ├── shortcuts.ts # Keyboard shortcuts │ └── exports.ts # Export templates ├── styles/ │ └── globals.css # Global styles + Tailwind ├── public/ │ ├── favicon.ico │ ├── og-image.png │ └── icons/ ├── tests/ │ ├── unit/ # Vitest unit tests │ │ ├── utils/ │ │ └── components/ │ └── e2e/ # Playwright E2E tests │ ├── playground.spec.ts │ └── palettes.spec.ts ├── .github/ │ └── workflows/ │ └── ci.yml # CI/CD pipeline ├── package.json ├── tsconfig.json ├── tailwind.config.ts # Tailwind CSS 4 config ├── next.config.ts # Next.js 16 config ├── vitest.config.ts # Vitest config ├── playwright.config.ts # Playwright config ├── .env.example ├── .env.local # Local environment variables ├── .gitignore ├── .eslintrc.json ├── .prettierrc ├── CLAUDE.md # This file └── README.md # Project overview ``` ## Key Features ### 1. Color Playground **Location**: `/` or `/playground` Interactive color manipulation interface with: - Real-time color picker - Support for all input formats (hex, rgb, hsl, hsv, lab, oklab, lch, oklch, cmyk, gray, named) - Live color information display (all format representations) - Format conversion with copy-to-clipboard - Color manipulation controls (lighten, darken, saturate, desaturate, rotate, complement, grayscale) - Color mixing with ratio slider - Visual preview with large swatch **Key Components**: - `ColorPicker` - Main input component - `ColorDisplay` - Large preview area - `ColorInfo` - Tabbed format information - `ManipulationPanel` - Slider controls for adjustments ### 2. Palette Generation **Location**: `/palettes/*` Multiple palette generation tools: **a) Harmony Palettes** (`/palettes/harmony`) - Monochromatic (1 base color) - Analogous (3 adjacent colors) - Complementary (2 opposite colors) - Split-complementary (3 colors) - Triadic (3 evenly spaced colors) - Tetradic (4 colors in rectangle) - Custom harmony angles **b) Distinct Colors** (`/palettes/distinct`) - Generate N visually distinct colors - Configurable count (2-100) - Distance metric selection (CIE76, CIEDE2000) - Optional fixed colors to include - Progress indicator (can take 2-5 minutes) **c) Gradient Creator** (`/palettes/gradient`) - Multiple color stops - Configurable step count - Color space selection (RGB, HSL, Lab, LCH, OkLab, OkLCH) - Live preview - Export as CSS gradient or color array **Key Components**: - `PaletteGenerator` - Main palette UI - `GradientCreator` - Gradient builder - `DistinctGenerator` - Distinct colors with progress - `PaletteGrid` - Visual palette display - `ExportMenu` - Export in multiple formats ### 3. Accessibility Tools **Location**: `/accessibility/*` **a) Contrast Checker** (`/accessibility/contrast`) - WCAG 2.1 compliance analysis - AA/AAA ratings for normal and large text - Foreground/background color inputs - Contrast ratio calculation - Recommendations for improvement **b) Color Blindness Simulator** (`/accessibility/colorblind`) - Simulate protanopia (red-blind) - Simulate deuteranopia (green-blind) - Simulate tritanopia (blue-blind) - Side-by-side comparison - Batch simulation for palettes **c) Text Color Optimizer** - Find optimal text color for background - WCAG compliance guarantee - Light/dark text selection **Key Components**: - `AccessibilityChecker` - Main A11y interface - `ContrastAnalyzer` - WCAG contrast checker - `ColorBlindSimulator` - Simulation previews - `ComplianceBadge` - AA/AAA status indicators ### 4. Named Colors Explorer **Location**: `/names` Browse and search 148 CSS/X11 named colors: - Searchable grid of color swatches - Filter by name, hex value - Sort by hue, brightness, saturation, name - Click to use in playground - Find nearest named color for any input **Key Components**: - `NamedColorsGrid` - Visual grid display - `ColorSearch` - Search and filter - `NearestColorFinder` - Find closest named color ### 5. Batch Operations **Location**: `/batch` Process multiple colors at once: - Upload CSV/JSON files with color lists - Apply operations to all colors (lighten, darken, etc.) - Bulk format conversion - Download results in multiple formats - Visual preview of batch results **Key Components**: - `BatchUploader` - File upload interface - `BatchOperations` - Operation selection - `BatchPreview` - Results preview - `BatchExporter` - Download results ## Architecture Patterns ### Server vs Client Components **Server Components** (default): - Page layouts - Static content - Data fetching (when possible) - SEO-critical content **Client Components** (use 'use client'): - Interactive UI (color pickers, sliders) - State management components - Animation/transition components - Browser API usage (localStorage, clipboard) ### State Management Strategy **Server State** (React Query): - API responses from Pastel API - Cached color operations - Automatic refetching and caching **Client State** (Zustand): - Current selected colors - Color history (session) - User preferences (theme, shortcuts) - UI state (sidebar open/closed) **Persistent State** (LocalStorage/IndexedDB): - Saved palettes - Long-term color history - User settings ### API Integration **Type-Safe Client**: ```typescript // lib/api/client.ts export class PastelAPIClient { private baseURL: string; constructor(baseURL: string = process.env.NEXT_PUBLIC_API_URL!) { this.baseURL = baseURL; } async getColorInfo(colors: string[]): Promise { const response = await fetch(`${this.baseURL}/api/v1/colors/info`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ colors }), }); if (!response.ok) throw new Error('API request failed'); return response.json(); } // ... all 21 endpoints } export const pastelAPI = new PastelAPIClient(); ``` **React Query Hooks**: ```typescript // lib/api/queries.ts export const useColorInfo = (colors: string[]) => { return useQuery({ queryKey: ['colorInfo', colors], queryFn: () => pastelAPI.getColorInfo(colors), enabled: colors.length > 0, }); }; export const useDistinctColors = () => { return useMutation({ mutationFn: (params: DistinctColorsParams) => pastelAPI.generateDistinct(params.count, params.metric), }); }; ``` ### Routing Strategy **Next.js 16 App Router**: - File-based routing in `app/` directory - Server Components by default - Nested layouts for shared UI - Loading states with `loading.tsx` - Error boundaries with `error.tsx` - Metadata API for SEO **Example Route Structure**: ``` app/ ├── layout.tsx # Root layout (navbar, providers) ├── page.tsx # Home page ├── playground/ │ ├── layout.tsx # Playground layout (sidebar) │ └── page.tsx # Playground content └── palettes/ ├── layout.tsx # Shared palette layout ├── page.tsx # Palette dashboard └── harmony/ └── page.tsx # Harmony palettes ``` ## Development ### Setup ```bash # Clone repository git clone git@github.com:valknarness/pastel-ui.git cd pastel-ui # Install dependencies (requires Node.js 18+) pnpm install # Set up environment variables cp .env.example .env.local # Edit .env.local with your Pastel API URL # Run development server pnpm dev # Open http://localhost:3000 ``` ### Environment Variables ```bash # .env.local NEXT_PUBLIC_API_URL=http://localhost:3000 # Pastel API URL NEXT_PUBLIC_APP_URL=http://localhost:3000 # This app URL ``` ### Available Scripts ```bash # Development pnpm dev # Start dev server (localhost:3000) pnpm dev:turbo # Start with Turbopack # Building pnpm build # Production build pnpm start # Start production server # Code Quality pnpm lint # ESLint pnpm lint:fix # Fix ESLint issues pnpm format # Prettier formatting pnpm type-check # TypeScript check # Testing pnpm test # Run Vitest unit tests pnpm test:ui # Vitest UI mode pnpm test:e2e # Playwright E2E tests pnpm test:e2e:ui # Playwright UI mode # Analysis pnpm analyze # Bundle size analysis ``` ### Code Style **TypeScript**: - Strict mode enabled - No `any` types (use `unknown` if needed) - Explicit return types for functions - Interface over type for object shapes **React**: - Functional components only - Custom hooks for reusable logic - Proper dependency arrays in hooks - Descriptive prop names **Naming Conventions**: - Components: `PascalCase` (e.g., `ColorPicker.tsx`) - Hooks: `camelCase` with `use` prefix (e.g., `useColor.ts`) - Utilities: `camelCase` (e.g., `formatColor.ts`) - Constants: `SCREAMING_SNAKE_CASE` (e.g., `MAX_COLORS`) - Types/Interfaces: `PascalCase` (e.g., `ColorInfo`) **File Organization**: - One component per file - Co-locate types with components - Group related utilities - Barrel exports (`index.ts`) for public APIs ## Tailwind CSS 4 Usage ### Configuration ```typescript // tailwind.config.ts import type { Config } from 'tailwindcss'; const config: Config = { content: [ './app/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: { colors: { // Custom color palette primary: 'oklch(var(--primary))', secondary: 'oklch(var(--secondary))', // ... using CSS variables for theming }, animation: { // Custom animations 'fade-in': 'fadeIn 0.2s ease-in', 'slide-up': 'slideUp 0.3s ease-out', }, }, }, plugins: [ require('@tailwindcss/forms'), require('@tailwindcss/typography'), ], }; export default config; ``` ### CSS-First Configuration (Tailwind 4) ```css /* app/globals.css */ @import "tailwindcss"; @theme { /* Custom design tokens */ --color-primary: oklch(0.7 0.15 250); --color-secondary: oklch(0.6 0.12 180); /* Spacing scale */ --spacing-xs: 0.5rem; --spacing-sm: 0.75rem; --spacing-md: 1rem; /* Animations */ --transition-fast: 150ms; --transition-normal: 300ms; } /* Dark mode overrides */ @media (prefers-color-scheme: dark) { @theme { --color-primary: oklch(0.8 0.15 250); --color-secondary: oklch(0.7 0.12 180); } } ``` ### Component Styling Pattern ```tsx // components/color/ColorSwatch.tsx import { cn } from '@/lib/utils/cn'; interface ColorSwatchProps { color: string; size?: 'sm' | 'md' | 'lg'; onClick?: () => void; } export function ColorSwatch({ color, size = 'md', onClick }: ColorSwatchProps) { return ( ); } ``` ## Testing Strategy ### Unit Tests (Vitest) ```typescript // tests/unit/utils/color.test.ts import { describe, it, expect } from 'vitest'; import { parseColor, formatColor } from '@/lib/utils/color'; describe('color utilities', () => { it('should parse hex colors', () => { expect(parseColor('#ff0099')).toEqual({ r: 255, g: 0, b: 153, }); }); it('should format RGB to hex', () => { expect(formatColor({ r: 255, g: 0, b: 153 }, 'hex')) .toBe('#ff0099'); }); }); ``` ### Component Tests ```typescript // tests/unit/components/ColorSwatch.test.tsx import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ColorSwatch } from '@/components/color/ColorSwatch'; describe('ColorSwatch', () => { it('should render with correct background color', () => { render(); const swatch = screen.getByRole('button'); expect(swatch).toHaveStyle({ backgroundColor: '#ff0099' }); }); it('should call onClick when clicked', async () => { const onClick = vi.fn(); render(); await userEvent.click(screen.getByRole('button')); expect(onClick).toHaveBeenCalledOnce(); }); }); ``` ### E2E Tests (Playwright) ```typescript // tests/e2e/playground.spec.ts import { test, expect } from '@playwright/test'; test('color manipulation workflow', async ({ page }) => { await page.goto('/playground'); // Enter a color await page.fill('[aria-label="Color input"]', '#ff0099'); // Verify color info is displayed await expect(page.locator('text=rgb(255, 0, 153)')).toBeVisible(); // Adjust lightness await page.locator('[aria-label="Lightness slider"]').fill('0.7'); // Verify updated color await expect(page.locator('.color-display')).toHaveCSS( 'background-color', /rgb\(255, \d+, \d+\)/ ); }); ``` ## Deployment ### Vercel (Recommended) ```bash # Install Vercel CLI pnpm add -g vercel # Deploy vercel # Production deployment vercel --prod ``` ### Environment Variables (Production) ```bash # Vercel dashboard or .env.production NEXT_PUBLIC_API_URL=https://api.pastel.example.com NEXT_PUBLIC_APP_URL=https://pastel.example.com ``` ### Build Optimization ```typescript // next.config.ts import type { NextConfig } from 'next'; const nextConfig: NextConfig = { // Enable React Compiler (Next.js 16) experimental: { reactCompiler: true, }, // Image optimization images: { formats: ['image/avif', 'image/webp'], }, // Bundle analyzer (conditional) ...(process.env.ANALYZE === 'true' && { webpack(config) { const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); config.plugins.push( new BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false, }) ); return config; }, }), }; export default nextConfig; ``` ### CI/CD Pipeline ```yaml # .github/workflows/ci.yml name: CI/CD on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 with: version: 9 - uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' - run: pnpm install - run: pnpm lint - run: pnpm type-check - run: pnpm test - run: pnpm build e2e: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 - uses: actions/setup-node@v4 - run: pnpm install - run: pnpm build - run: pnpm test:e2e deploy: needs: [test, e2e] if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} vercel-args: '--prod' ``` ## Common Patterns ### Custom Hook Example ```typescript // lib/hooks/useColor.ts import { useState, useCallback } from 'use'; import { useColorInfo } from '@/lib/api/queries'; import { parseColor } from '@/lib/utils/color'; export function useColor(initialColor?: string) { const [color, setColor] = useState(initialColor ?? '#000000'); const [format, setFormat] = useState('hex'); const { data: colorInfo, isLoading } = useColorInfo([color]); const updateColor = useCallback((newColor: string) => { try { const parsed = parseColor(newColor); if (parsed) { setColor(newColor); } } catch (error) { console.error('Invalid color:', error); } }, []); return { color, colorInfo: colorInfo?.data.colors[0], format, setFormat, updateColor, isLoading, }; } ``` ### Zustand Store Example ```typescript // lib/stores/historyStore.ts import { create } from 'zustand'; import { persist } from 'zustand/middleware'; interface ColorHistoryState { history: string[]; addColor: (color: string) => void; clearHistory: () => void; } export const useColorHistory = create()( persist( (set) => ({ history: [], addColor: (color) => set((state) => ({ history: [color, ...state.history.filter(c => c !== color)].slice(0, 50), })), clearHistory: () => set({ history: [] }), }), { name: 'color-history', storage: createJSONStorage(() => localStorage), } ) ); ``` ## Troubleshooting ### Common Issues **Issue**: API calls failing with CORS errors **Solution**: Ensure `NEXT_PUBLIC_API_URL` is set correctly and the Pastel API has CORS enabled **Issue**: Tailwind classes not applying **Solution**: Check `content` paths in `tailwind.config.ts` include all component files **Issue**: Hydration errors **Solution**: Ensure client components using browser APIs have proper checks: ```tsx const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); if (!mounted) return null; ``` **Issue**: Large bundle size **Solution**: Use dynamic imports for heavy components and run `pnpm analyze` ## Resources ### Documentation - [Next.js 16 Docs](https://nextjs.org/docs) - [React 19 Docs](https://react.dev) - [Tailwind CSS 4 Docs](https://tailwindcss.com/docs) - [React Query Docs](https://tanstack.com/query/latest) - [Pastel API Docs](https://github.com/valknarness/pastel-api) ### Color Science - [OkLab Color Space](https://bottosson.github.io/posts/oklab/) - [WCAG Contrast Guidelines](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html) - [Color Blindness Simulation](https://www.color-blindness.com/coblis-color-blindness-simulator/) ### Tools - [Figma](https://www.figma.com) - Design mockups - [Playwright](https://playwright.dev) - E2E testing - [Vitest](https://vitest.dev) - Unit testing ## Contributing ### PR Checklist - [ ] Tests pass (`pnpm test`) - [ ] E2E tests pass (`pnpm test:e2e`) - [ ] No ESLint errors (`pnpm lint`) - [ ] No TypeScript errors (`pnpm type-check`) - [ ] Code formatted (`pnpm format`) - [ ] Documentation updated (if needed) - [ ] Accessibility tested (keyboard, screen reader) - [ ] Performance checked (bundle size) ### Code Review Guidelines - All interactive elements must be keyboard accessible - All images must have alt text - Color contrast must meet WCAG AA - No console.log in production code - Meaningful variable/function names - Comments for complex logic only --- **Project Status**: Design phase complete, ready for implementation **Last Updated**: 2025-11-07