-
-
Colors to Test
+
+
+ Colors to Test
Add Color
-
+
-
+
{colors.map((color, index) => (
@@ -112,12 +113,14 @@ export default function ColorBlindPage() {
)}
))}
-
-
+
+
-
-
Blindness Type
-
+
+
+ Blindness Type
+
+
setBlindnessType(value as ColorBlindnessType)}
@@ -153,68 +156,76 @@ export default function ColorBlindPage() {
>
)}
-
-
+
+
{simulations.length > 0 ? (
<>
-
-
Simulation Results
-
- Compare original colors (left) with how they appear to people with{' '}
- {blindnessType} (right)
-
+
+
+ Simulation Results
+
+
+
+ Compare original colors (left) with how they appear to people with{' '}
+ {blindnessType} (right)
+
-
- {simulations.map((sim, index) => (
-
-
-
- Original
-
-
-
-
{sim.input}
+
+ {simulations.map((sim, index) => (
+
+
+
+ Original
+
+
+
+ {sim.input}
+
+
+
+
+
+ As Seen ({sim.difference_percentage.toFixed(1)}% difference)
+
+
+
+ {sim.output}
+
+ ))}
+
+
+
-
-
- As Seen ({sim.difference_percentage.toFixed(1)}% difference)
-
-
-
- {sim.output}
-
-
-
- ))}
-
-
-
-
-
-
- Accessibility Tip
-
-
- Ensure important information isn't conveyed by color alone. Use text
- labels, patterns, or icons to make your design accessible to everyone
-
-
+
+
+
+
+ Accessibility Tip
+
+
+ Ensure important information isn't conveyed by color alone. Use text
+ labels, patterns, or icons to make your design accessible to everyone
+
+
+
>
) : (
-
-
-
Add colors and click Simulate to see how they appear
-
with different types of color blindness
-
+
+
+
+ Add colors and click Simulate to see how they appear
+ with different types of color blindness
+
+
)}
diff --git a/app/(app)/pastel/contrast/page.tsx b/app/(app)/pastel/contrast/page.tsx
index 40f3e3c..e9c19c6 100644
--- a/app/(app)/pastel/contrast/page.tsx
+++ b/app/(app)/pastel/contrast/page.tsx
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
import { ColorPicker } from '@/components/pastel/ColorPicker';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { getContrastRatio, hexToRgb, checkWCAGCompliance } from '@/lib/pastel/utils/color';
import { ArrowLeftRight, Check, X } from 'lucide-react';
@@ -68,12 +69,14 @@ export default function ContrastPage() {
{/* Color Pickers */}
-
-
-
Foreground Color
-
-
-
+
+
+ Foreground Color
+
+
+
+
+
-
-
Background Color
-
-
+
+
+ Background Color
+
+
+
+
+
{/* Results */}
{/* Preview */}
-
-
Preview
-
-
Normal Text (16px)
-
Large Text (24px)
-
-
+
+
+ Preview
+
+
+
+
Normal Text (16px)
+
Large Text (24px)
+
+
+
{/* Contrast Ratio */}
{ratio !== null && (
-
-
Contrast Ratio
-
-
{ratio.toFixed(2)}:1
-
- {ratio >= 7
- ? 'Excellent contrast'
- : ratio >= 4.5
- ? 'Good contrast'
- : ratio >= 3
- ? 'Minimum contrast'
- : 'Poor contrast'}
-
-
-
+
+
+ Contrast Ratio
+
+
+
+
{ratio.toFixed(2)}:1
+
+ {ratio >= 7
+ ? 'Excellent contrast'
+ : ratio >= 4.5
+ ? 'Good contrast'
+ : ratio >= 3
+ ? 'Minimum contrast'
+ : 'Poor contrast'}
+
+
+
+
)}
{/* WCAG Compliance */}
{compliance && (
-
-
WCAG 2.1 Compliance
-
+
+
+ WCAG 2.1 Compliance
+
+
Level AA
@@ -161,8 +178,8 @@ export default function ContrastPage() {
/>
-
-
+
+
)}
diff --git a/app/(app)/pastel/distinct/page.tsx b/app/(app)/pastel/distinct/page.tsx
index ac10c35..5196cfa 100644
--- a/app/(app)/pastel/distinct/page.tsx
+++ b/app/(app)/pastel/distinct/page.tsx
@@ -12,6 +12,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useGenerateDistinct } from '@/lib/pastel/api/queries';
import { Loader2 } from 'lucide-react';
import { toast } from 'sonner';
@@ -49,82 +50,90 @@ export default function DistinctPage() {
{/* Controls */}
-
-
-
Settings
-
+
+
+ Settings
+
-
-
- Number of Colors
-
-
setCount(parseInt(e.target.value))}
- />
-
- Higher counts take longer to generate
-
-
-
-
-
- Distance Metric
-
- setMetric(value as 'cie76' | 'ciede2000')}
- >
-
-
-
-
- CIE76 (Faster)
- CIEDE2000 (More Accurate)
-
-
-
-
-
- {generateMutation.isPending ? (
- <>
-
- Generating..
- >
- ) : (
- 'Generate Colors'
- )}
-
-
- {generateMutation.isPending && (
-
- This may take a few moments..
+
+
+
+ Number of Colors
+
+
setCount(parseInt(e.target.value))}
+ />
+
+ Higher counts take longer to generate
+
- )}
-
+
+
+
+ Distance Metric
+
+ setMetric(value as 'cie76' | 'ciede2000')}
+ >
+
+
+
+
+ CIE76 (Faster)
+ CIEDE2000 (More Accurate)
+
+
+
+
+
+ {generateMutation.isPending ? (
+ <>
+
+ Generating..
+ >
+ ) : (
+ 'Generate Colors'
+ )}
+
+
+ {generateMutation.isPending && (
+
+ This may take a few moments..
+
+ )}
+
+
{/* Results */}
-
-
- Generated Colors {colors.length > 0 && `(${colors.length})`}
-
-
-
+
+
+
+ Generated Colors {colors.length > 0 && `(${colors.length})`}
+
+
+
+
+
+
{colors.length > 0 && (
-
-
-
+
+
+
+
+
)}
diff --git a/app/(app)/pastel/gradient/page.tsx b/app/(app)/pastel/gradient/page.tsx
index a23b29a..1f9377c 100644
--- a/app/(app)/pastel/gradient/page.tsx
+++ b/app/(app)/pastel/gradient/page.tsx
@@ -6,6 +6,7 @@ import { PaletteGrid } from '@/components/pastel/PaletteGrid';
import { ExportMenu } from '@/components/pastel/ExportMenu';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useGenerateGradient } from '@/lib/pastel/api/queries';
import { Loader2, Plus, X } from 'lucide-react';
import { toast } from 'sonner';
@@ -59,39 +60,45 @@ export default function GradientPage() {
{/* Controls */}
-
-
Color Stops
-
- {stops.map((stop, index) => (
-
-
-
updateStop(index, color)}
- />
+
+
+ Color Stops
+
+
+
+ {stops.map((stop, index) => (
+
+
+ updateStop(index, color)}
+ />
+
+ {stops.length > 2 && (
+
removeStop(index)}
+ className="mt-8"
+ >
+
+
+ )}
- {stops.length > 2 && (
-
removeStop(index)}
- className="mt-8"
- >
-
-
- )}
-
- ))}
-
-
- Add Stop
-
-
-
+ ))}
+
+
+ Add Stop
+
+
+
+
-
-
Settings
-
+
+
+ Settings
+
+
Number of Colors
@@ -120,34 +127,44 @@ export default function GradientPage() {
'Generate Gradient'
)}
-
-
+
+
{/* Preview */}
{gradient && gradient.length > 0 && (
<>
-
+
+
+ Gradient Preview
+
+
+
+
+
-
-
- Colors ({gradient.length})
-
-
-
+
+
+
+ Colors ({gradient.length})
+
+
+
+
+
+
-
-
-
+
+
+
+
+
>
)}
diff --git a/app/(app)/pastel/harmony/page.tsx b/app/(app)/pastel/harmony/page.tsx
index 6a3ccb3..10a0e2f 100644
--- a/app/(app)/pastel/harmony/page.tsx
+++ b/app/(app)/pastel/harmony/page.tsx
@@ -12,6 +12,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useGeneratePalette } from '@/lib/pastel/api/queries';
import { Loader2, Palette } from 'lucide-react';
import { toast } from 'sonner';
@@ -70,14 +71,20 @@ export default function HarmonyPage() {
{/* Controls */}
-
-
Base Color
-
-
+
+
+ Base Color
+
+
+
+
+
-
-
Harmony Type
-
+
+
+ Harmony Type
+
+
setHarmonyType(value as HarmonyType)}
@@ -116,32 +123,40 @@ export default function HarmonyPage() {
>
)}
-
-
+
+
{/* Results */}
{palette.length > 0 && (
<>
-
-
- Generated Palette ({palette.length} colors)
-
-
-
+
+
+
+ Generated Palette ({palette.length} colors)
+
+
+
+
+
+
-
-
-
+
+
+
+
+
>
)}
{palette.length === 0 && (
-
-
-
Select a harmony type and click Generate to create your palette
-
+
+
+
+ Select a harmony type and click Generate to create your palette
+
+
)}
diff --git a/app/(app)/pastel/names/page.tsx b/app/(app)/pastel/names/page.tsx
index d62064e..1e44305 100644
--- a/app/(app)/pastel/names/page.tsx
+++ b/app/(app)/pastel/names/page.tsx
@@ -10,6 +10,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
+import { Card, CardContent } from '@/components/ui/card';
import { useNamedColors } from '@/lib/pastel/api/queries';
import { Loader2 } from 'lucide-react';
import { parse_color } from '@valknarthing/pastel-wasm';
@@ -76,46 +77,48 @@ export default function NamedColorsPage() {
{/* Colors Grid */}
-
- {isLoading && (
-
-
-
- )}
-
- {isError && (
-
- Failed to load named colors
-
- )}
-
- {filteredColors.length > 0 && (
- <>
-
- Showing {filteredColors.length} colors
+
+
+ {isLoading && (
+
+
-
- {filteredColors.map((color) => (
-
-
-
-
{color.name}
-
- {color.hex}
+ )}
+
+ {isError && (
+
+ Failed to load named colors
+
+ )}
+
+ {filteredColors.length > 0 && (
+ <>
+
+ Showing {filteredColors.length} colors
+
+
+ {filteredColors.map((color) => (
+
+
+
+
{color.name}
+
+ {color.hex}
+
-
- ))}
-
- >
- )}
+ ))}
+
+ >
+ )}
- {filteredColors.length === 0 && !isLoading && !isError && (
-
- No colors match your search
-
- )}
-
+ {filteredColors.length === 0 && !isLoading && !isError && (
+
+ No colors match your search
+
+ )}
+
+
);
diff --git a/app/(app)/pastel/page.tsx b/app/(app)/pastel/page.tsx
index b4b941f..216d76e 100644
--- a/app/(app)/pastel/page.tsx
+++ b/app/(app)/pastel/page.tsx
@@ -6,6 +6,7 @@ import { ColorPicker } from '@/components/pastel/ColorPicker';
import { ColorDisplay } from '@/components/pastel/ColorDisplay';
import { ColorInfo } from '@/components/pastel/ColorInfo';
import { ManipulationPanel } from '@/components/pastel/ManipulationPanel';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useColorInfo } from '@/lib/pastel/api/queries';
import { useColorHistory } from '@/lib/pastel/stores/historyStore';
import { Loader2, Share2, History, X } from 'lucide-react';
@@ -84,30 +85,36 @@ function PlaygroundContent() {
{/* Left Column: Color Picker and Display */}
-
-
Color Picker
-
-
+
+
+ Color Picker
+
+
+
+
+
-
-
-
Preview
+
+
+ Preview
Share
-
-
-
-
-
+
+
+
+
+
+
+
{recentColors.length > 0 && (
-
-
+
+
-
Recent Colors
+ Recent Colors
Clear
-
-
- {recentColors.map((entry) => (
-
setColor(entry.color)}
- title={entry.color}
- role="button"
- tabIndex={0}
- onKeyDown={(e) => {
- if (e.key === 'Enter' || e.key === ' ') {
- setColor(entry.color);
- }
- }}
- >
-
-
{
- e.stopPropagation();
- removeColor(entry.color);
- toast.success('Color removed from history');
- }}
- className="p-1 bg-destructive rounded-full hover:bg-destructive/80"
- aria-label="Remove color"
- >
-
-
+
+
+
+ {recentColors.map((entry) => (
+
setColor(entry.color)}
+ title={entry.color}
+ role="button"
+ tabIndex={0}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ setColor(entry.color);
+ }
+ }}
+ >
+
+ {
+ e.stopPropagation();
+ removeColor(entry.color);
+ toast.success('Color removed from history');
+ }}
+ className="p-1 bg-destructive rounded-full hover:bg-destructive/80"
+ aria-label="Remove color"
+ >
+
+
+
-
- ))}
-
-
+ ))}
+
+
+
)}
{/* Right Column: Color Information */}
-
-
Color Information
+
+
+ Color Information
+
+
+ {isLoading && (
+
+
+
+ )}
- {isLoading && (
-
-
-
- )}
+ {isError && (
+
+
Error loading color information
+
{error?.message || 'Unknown error'}
+
+ )}
- {isError && (
-
-
Error loading color information
-
{error?.message || 'Unknown error'}
-
- )}
+ {colorInfo && }
+
+
- {colorInfo &&
}
-
-
-
-
Color Manipulation
-
-
+
+
+ Color Manipulation
+
+
+
+
+
diff --git a/app/(app)/pastel/textcolor/page.tsx b/app/(app)/pastel/textcolor/page.tsx
index b4d139e..8b07423 100644
--- a/app/(app)/pastel/textcolor/page.tsx
+++ b/app/(app)/pastel/textcolor/page.tsx
@@ -4,6 +4,7 @@ import { useState } from 'react';
import { ColorPicker } from '@/components/pastel/ColorPicker';
import { ColorDisplay } from '@/components/pastel/ColorDisplay';
import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useTextColor } from '@/lib/pastel/api/queries';
import { Loader2, Palette, Plus, X, CheckCircle2, XCircle } from 'lucide-react';
import { toast } from 'sonner';
@@ -66,9 +67,9 @@ export default function TextColorPage() {
{/* Input */}
-
-
-
Background Colors
+
+
+ Background Colors
= 10}
@@ -78,140 +79,150 @@ export default function TextColorPage() {
Add
-
+
-
- {backgrounds.map((color, index) => (
-
-
-
updateBackground(index, newColor)}
- />
+
+
+ {backgrounds.map((color, index) => (
+
+
+ updateBackground(index, newColor)}
+ />
+
+ {backgrounds.length > 1 && (
+
removeBackground(index)}
+ variant="ghost"
+ size="icon"
+ >
+
+
+ )}
- {backgrounds.length > 1 && (
-
removeBackground(index)}
- variant="ghost"
- size="icon"
- >
-
-
- )}
-
- ))}
-
+ ))}
+
-
- {textColorMutation.isPending ? (
- <>
-
- Optimizing..
- >
- ) : (
- <>
-
- Optimize Text Colors
- >
- )}
-
-
+
+ {textColorMutation.isPending ? (
+ <>
+
+ Optimizing..
+ >
+ ) : (
+ <>
+
+ Optimize Text Colors
+ >
+ )}
+
+
+
-
-
How it works
-
- This tool analyzes each background color and automatically selects either black
- or white text to ensure maximum readability. The algorithm guarantees WCAG AA
- compliance (4.5:1 contrast ratio) for normal text
-
-
+
+
+ How it works
+
+ This tool analyzes each background color and automatically selects either black
+ or white text to ensure maximum readability. The algorithm guarantees WCAG AA
+ compliance (4.5:1 contrast ratio) for normal text
+
+
+
{/* Results */}
{results.length > 0 ? (
<>
-
-
Optimized Results
-
+
+
+ Optimized Results
+
+
{results.map((result, index) => (
-
-
-
-
-
- {result.background}
-
+
+
+
+
+
+ {result.background}
+
+
-
-
-
- Sample Text Preview
-
-
- The quick brown fox jumps over the lazy dog. This is how your text
- will look on this background color
-
-
+
+
+ Sample Text Preview
+
+
+ The quick brown fox jumps over the lazy dog. This is how your text
+ will look on this background color
+
+
-
-
-
Text Color:
-
{result.textcolor}
+
+
+ Text Color:
+ {result.textcolor}
+
+
+ Contrast:
+
+ {result.contrast_ratio.toFixed(2)}:1
+
+
+
+ {result.wcag_aa ? (
+
+ ) : (
+
+ )}
+
+ WCAG AA
+
+
+
+ {result.wcag_aaa ? (
+
+ ) : (
+
+ )}
+
+ WCAG AAA
+
+
-
- Contrast:
-
- {result.contrast_ratio.toFixed(2)}:1
-
-
-
- {result.wcag_aa ? (
-
- ) : (
-
- )}
-
- WCAG AA
-
-
-
- {result.wcag_aaa ? (
-
- ) : (
-
- )}
-
- WCAG AAA
-
-
-
-
+
+
))}
-
-
+
+
>
) : (
-
-
-
Add background colors and click Optimize to see results
-
+
+
+
+ Add background colors and click Optimize to see results
+
+
)}