2025-11-07 10:30:29 +01:00
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Overview
feat: integrate WebAssembly for zero-latency, offline-first color operations
Replaced REST API dependency with @valknarthing/pastel-wasm (130KB) for
complete browser-based color operations. The application is now fully
static (2.2MB total) with zero network latency and offline support.
**Key Changes:**
1. **WASM Integration:**
- Added @valknarthing/pastel-wasm dependency (0.1.0)
- Created lib/api/wasm-client.ts wrapper matching API interface
- Updated lib/api/client.ts to use WASM client by default
- All 18 color operations now run locally in browser
2. **Static Export Configuration:**
- Changed next.config.ts output from 'standalone' to 'export'
- Disabled image optimization for static export
- Removed API proxy route (app/api/pastel/[...path]/route.ts)
- Updated package.json scripts (removed dev:api, added serve)
3. **Docker Optimization:**
- Migrated from Node.js standalone to nginx-alpine
- Created nginx.conf with SPA routing and WASM mime types
- Updated Dockerfile for static file serving
- Reduced image size from ~150MB to ~25MB
- Changed port from 3000 to 80 (standard HTTP)
- Simplified docker-compose.yml (removed pastel-api service)
4. **Documentation Updates:**
- Updated README.md with WASM benefits and deployment options
- Added Key Benefits section highlighting zero-latency features
- Rewrote deployment section for static hosting platforms
- Updated CLAUDE.md tech stack and architecture
- Removed obsolete docs: DEV_SETUP.md, DOCKER.md, IMPLEMENTATION_PLAN.md
**Benefits:**
- 🚀 Zero Latency - All operations run locally via WebAssembly
- 📱 Offline First - Works completely offline after initial load
- 🌐 No Backend - Fully static, deploy anywhere
- ⚡ Fast - Native-speed color operations in browser
- 📦 Small - 2.2MB total (130KB WASM, 2.07MB HTML/CSS/JS)
**Deployment:**
Can now be deployed to any static hosting platform:
- Vercel, Netlify, Cloudflare Pages (zero config)
- GitHub Pages, S3, CDN
- Self-hosted nginx/Apache
- Docker (optional, nginx-based)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 09:06:25 +01:00
**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.
2025-11-07 10:30:29 +01:00
**Technology Stack:**
feat: integrate WebAssembly for zero-latency, offline-first color operations
Replaced REST API dependency with @valknarthing/pastel-wasm (130KB) for
complete browser-based color operations. The application is now fully
static (2.2MB total) with zero network latency and offline support.
**Key Changes:**
1. **WASM Integration:**
- Added @valknarthing/pastel-wasm dependency (0.1.0)
- Created lib/api/wasm-client.ts wrapper matching API interface
- Updated lib/api/client.ts to use WASM client by default
- All 18 color operations now run locally in browser
2. **Static Export Configuration:**
- Changed next.config.ts output from 'standalone' to 'export'
- Disabled image optimization for static export
- Removed API proxy route (app/api/pastel/[...path]/route.ts)
- Updated package.json scripts (removed dev:api, added serve)
3. **Docker Optimization:**
- Migrated from Node.js standalone to nginx-alpine
- Created nginx.conf with SPA routing and WASM mime types
- Updated Dockerfile for static file serving
- Reduced image size from ~150MB to ~25MB
- Changed port from 3000 to 80 (standard HTTP)
- Simplified docker-compose.yml (removed pastel-api service)
4. **Documentation Updates:**
- Updated README.md with WASM benefits and deployment options
- Added Key Benefits section highlighting zero-latency features
- Rewrote deployment section for static hosting platforms
- Updated CLAUDE.md tech stack and architecture
- Removed obsolete docs: DEV_SETUP.md, DOCKER.md, IMPLEMENTATION_PLAN.md
**Benefits:**
- 🚀 Zero Latency - All operations run locally via WebAssembly
- 📱 Offline First - Works completely offline after initial load
- 🌐 No Backend - Fully static, deploy anywhere
- ⚡ Fast - Native-speed color operations in browser
- 📦 Small - 2.2MB total (130KB WASM, 2.07MB HTML/CSS/JS)
**Deployment:**
Can now be deployed to any static hosting platform:
- Vercel, Netlify, Cloudflare Pages (zero config)
- GitHub Pages, S3, CDN
- Self-hosted nginx/Apache
- Docker (optional, nginx-based)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 09:06:25 +01:00
- **Framework**: Next.js 16 (App Router, React 19, Static Export)
2025-11-07 10:30:29 +01:00
- **Styling**: Tailwind CSS 4 (CSS-first configuration)
- **Language**: TypeScript (strict mode)
feat: integrate WebAssembly for zero-latency, offline-first color operations
Replaced REST API dependency with @valknarthing/pastel-wasm (130KB) for
complete browser-based color operations. The application is now fully
static (2.2MB total) with zero network latency and offline support.
**Key Changes:**
1. **WASM Integration:**
- Added @valknarthing/pastel-wasm dependency (0.1.0)
- Created lib/api/wasm-client.ts wrapper matching API interface
- Updated lib/api/client.ts to use WASM client by default
- All 18 color operations now run locally in browser
2. **Static Export Configuration:**
- Changed next.config.ts output from 'standalone' to 'export'
- Disabled image optimization for static export
- Removed API proxy route (app/api/pastel/[...path]/route.ts)
- Updated package.json scripts (removed dev:api, added serve)
3. **Docker Optimization:**
- Migrated from Node.js standalone to nginx-alpine
- Created nginx.conf with SPA routing and WASM mime types
- Updated Dockerfile for static file serving
- Reduced image size from ~150MB to ~25MB
- Changed port from 3000 to 80 (standard HTTP)
- Simplified docker-compose.yml (removed pastel-api service)
4. **Documentation Updates:**
- Updated README.md with WASM benefits and deployment options
- Added Key Benefits section highlighting zero-latency features
- Rewrote deployment section for static hosting platforms
- Updated CLAUDE.md tech stack and architecture
- Removed obsolete docs: DEV_SETUP.md, DOCKER.md, IMPLEMENTATION_PLAN.md
**Benefits:**
- 🚀 Zero Latency - All operations run locally via WebAssembly
- 📱 Offline First - Works completely offline after initial load
- 🌐 No Backend - Fully static, deploy anywhere
- ⚡ Fast - Native-speed color operations in browser
- 📦 Small - 2.2MB total (130KB WASM, 2.07MB HTML/CSS/JS)
**Deployment:**
Can now be deployed to any static hosting platform:
- Vercel, Netlify, Cloudflare Pages (zero config)
- GitHub Pages, S3, CDN
- Self-hosted nginx/Apache
- Docker (optional, nginx-based)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 09:06:25 +01:00
- **Color Engine**: `@valknarthing/pastel-wasm` (WebAssembly, 130KB)
2025-11-07 10:30:29 +01:00
- **State Management**:
- `@tanstack/react-query` (server state)
- `zustand` (client state)
- **Animation**: `framer-motion`
- **Icons**: `lucide-react`
- **UI Components**: Custom components + shadcn/ui patterns
feat: integrate WebAssembly for zero-latency, offline-first color operations
Replaced REST API dependency with @valknarthing/pastel-wasm (130KB) for
complete browser-based color operations. The application is now fully
static (2.2MB total) with zero network latency and offline support.
**Key Changes:**
1. **WASM Integration:**
- Added @valknarthing/pastel-wasm dependency (0.1.0)
- Created lib/api/wasm-client.ts wrapper matching API interface
- Updated lib/api/client.ts to use WASM client by default
- All 18 color operations now run locally in browser
2. **Static Export Configuration:**
- Changed next.config.ts output from 'standalone' to 'export'
- Disabled image optimization for static export
- Removed API proxy route (app/api/pastel/[...path]/route.ts)
- Updated package.json scripts (removed dev:api, added serve)
3. **Docker Optimization:**
- Migrated from Node.js standalone to nginx-alpine
- Created nginx.conf with SPA routing and WASM mime types
- Updated Dockerfile for static file serving
- Reduced image size from ~150MB to ~25MB
- Changed port from 3000 to 80 (standard HTTP)
- Simplified docker-compose.yml (removed pastel-api service)
4. **Documentation Updates:**
- Updated README.md with WASM benefits and deployment options
- Added Key Benefits section highlighting zero-latency features
- Rewrote deployment section for static hosting platforms
- Updated CLAUDE.md tech stack and architecture
- Removed obsolete docs: DEV_SETUP.md, DOCKER.md, IMPLEMENTATION_PLAN.md
**Benefits:**
- 🚀 Zero Latency - All operations run locally via WebAssembly
- 📱 Offline First - Works completely offline after initial load
- 🌐 No Backend - Fully static, deploy anywhere
- ⚡ Fast - Native-speed color operations in browser
- 📦 Small - 2.2MB total (130KB WASM, 2.07MB HTML/CSS/JS)
**Deployment:**
Can now be deployed to any static hosting platform:
- Vercel, Netlify, Cloudflare Pages (zero config)
- GitHub Pages, S3, CDN
- Self-hosted nginx/Apache
- Docker (optional, nginx-based)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 09:06:25 +01:00
- **Deployment**: Fully static (2.2MB total)
2025-11-07 10:30:29 +01:00
## 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<ColorInfoResponse> {
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 (
<button
className={cn(
'rounded-lg border-2 border-gray-300 transition-all hover:scale-110',
'focus:outline-none focus:ring-2 focus:ring-primary',
{
'h-8 w-8': size === 'sm',
'h-12 w-12': size === 'md',
'h-16 w-16': size === 'lg',
}
)}
style={{ backgroundColor: color }}
onClick={onClick}
aria-label={`Color swatch: ${color}` }
/>
);
}
```
## Performance Optimization
### Code Splitting
```tsx
// Lazy load heavy components
import dynamic from 'next/dynamic';
const DistinctGenerator = dynamic(
() => import('@/components/tools/DistinctGenerator '),
{
loading: () => <LoadingSpinner />,
ssr: false, // Client-only component
}
);
```
### Image Optimization
```tsx
import Image from 'next/image';
<Image
src="/og-image.png"
alt="Pastel UI"
width={1200}
height={630}
priority // Load eagerly for above-fold images
/>
```
### API Call Optimization
```tsx
// Debounce API calls during slider interactions
import { useDebouncedValue } from '@/lib/hooks/useDebouncedValue ';
const [lightness, setLightness] = useState(0.5);
const debouncedLightness = useDebouncedValue(lightness, 300);
const { data } = useColorInfo([color], {
enabled: !!color && debouncedLightness !== lightness,
});
```
### Bundle Size Management
- Keep initial bundle < 200KB
- Use dynamic imports for route-specific code
- Analyze bundle with `pnpm analyze`
- Tree-shake unused dependencies
## Accessibility
### WCAG Compliance
- **AA** minimum for all UI elements
- **AAA** target for text content
- Color contrast checker built into design system
- Never rely on color alone for information
### Keyboard Navigation
```tsx
// All interactive elements must be keyboard accessible
<div
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleClick();
}
}}
onClick={handleClick}
>
Click me
</div>
```
### ARIA Labels
```tsx
<input
type="text"
value={color}
onChange={(e) => setColor(e.target.value)}
aria-label="Color input (hex, rgb, hsl, etc.)"
aria-describedby="color-format-hint"
/>
<span id="color-format-hint" className="sr-only">
Enter a color in any format: hex, rgb, hsl, or named color
</span>
```
### Focus Management
```tsx
import { useEffect, useRef } from 'react';
function Dialog({ isOpen, onClose }: DialogProps) {
const closeButtonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (isOpen) {
closeButtonRef.current?.focus();
}
}, [isOpen]);
return (
<div role="dialog" aria-modal="true">
{/* Dialog content */}
<button ref={closeButtonRef} onClick={onClose}>
Close
</button>
</div>
);
}
```
## 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(<ColorSwatch color="#ff0099 " />);
const swatch = screen.getByRole('button');
expect(swatch).toHaveStyle({ backgroundColor: '#ff0099 ' });
});
it('should call onClick when clicked', async () => {
const onClick = vi.fn();
render(<ColorSwatch color="#ff0099 " onClick={onClick} />);
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<ColorFormat>('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<ColorHistoryState>()(
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