diff --git a/app/accessibility/colorblind/page.tsx b/app/accessibility/colorblind/page.tsx index 7131d3d..2b644a0 100644 --- a/app/accessibility/colorblind/page.tsx +++ b/app/accessibility/colorblind/page.tsx @@ -15,7 +15,7 @@ export default function ColorBlindPage() { const [colors, setColors] = useState(['#ff0099']); const [blindnessType, setBlindnessType] = useState('protanopia'); const [simulations, setSimulations] = useState< - Array<{ original: string; simulated: string }> + Array<{ input: string; output: string; difference_percentage: number }> >([]); const simulateMutation = useSimulateColorBlindness(); @@ -26,7 +26,7 @@ export default function ColorBlindPage() { colors, type: blindnessType, }); - setSimulations(result.simulations); + setSimulations(result.colors); toast.success(`Simulated ${blindnessType}`); } catch (error) { toast.error('Failed to simulate color blindness'); @@ -169,18 +169,18 @@ export default function ColorBlindPage() { Original

- - {sim.original} + + {sim.input}

- As Seen + As Seen ({sim.difference_percentage.toFixed(1)}% difference)

- - {sim.simulated} + + {sim.output}
diff --git a/app/accessibility/page.tsx b/app/accessibility/page.tsx index da9346f..230a45f 100644 --- a/app/accessibility/page.tsx +++ b/app/accessibility/page.tsx @@ -16,7 +16,6 @@ export default function AccessibilityPage() { href: '/accessibility/colorblind', icon: Eye, features: ['Protanopia', 'Deuteranopia', 'Tritanopia'], - comingSoon: true, }, { title: 'Text Color Optimizer', @@ -24,7 +23,6 @@ export default function AccessibilityPage() { href: '/accessibility/textcolor', icon: Palette, features: ['Automatic optimization', 'WCAG guaranteed', 'Light/dark options'], - comingSoon: true, }, ]; diff --git a/app/accessibility/textcolor/page.tsx b/app/accessibility/textcolor/page.tsx new file mode 100644 index 0000000..b7886da --- /dev/null +++ b/app/accessibility/textcolor/page.tsx @@ -0,0 +1,221 @@ +'use client'; + +import { useState } from 'react'; +import { ColorPicker } from '@/components/color/ColorPicker'; +import { ColorDisplay } from '@/components/color/ColorDisplay'; +import { Button } from '@/components/ui/button'; +import { useTextColor } from '@/lib/api/queries'; +import { Loader2, Palette, Plus, X, CheckCircle2, XCircle } from 'lucide-react'; +import { toast } from 'sonner'; + +export default function TextColorPage() { + const [backgrounds, setBackgrounds] = useState(['#ff0099']); + const [results, setResults] = useState< + Array<{ + background: string; + textcolor: string; + contrast_ratio: number; + wcag_aa: boolean; + wcag_aaa: boolean; + }> + >([]); + + const textColorMutation = useTextColor(); + + const handleOptimize = async () => { + try { + const result = await textColorMutation.mutateAsync({ + backgrounds, + }); + setResults(result.colors); + toast.success(`Optimized text colors for ${result.colors.length} background(s)`); + } catch (error) { + toast.error('Failed to optimize text colors'); + console.error(error); + } + }; + + const addBackground = () => { + if (backgrounds.length < 10) { + setBackgrounds([...backgrounds, '#000000']); + } + }; + + const removeBackground = (index: number) => { + if (backgrounds.length > 1) { + setBackgrounds(backgrounds.filter((_, i) => i !== index)); + } + }; + + const updateBackground = (index: number, color: string) => { + const newBackgrounds = [...backgrounds]; + newBackgrounds[index] = color; + setBackgrounds(newBackgrounds); + }; + + return ( +
+
+
+

Text Color Optimizer

+

+ Automatically find the best text color (black or white) for any background color +

+
+ +
+ {/* Input */} +
+
+
+

Background Colors

+ +
+ +
+ {backgrounds.map((color, index) => ( +
+
+ updateBackground(index, newColor)} + /> +
+ {backgrounds.length > 1 && ( + + )} +
+ ))} +
+ + +
+ +
+

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

+
+ {results.map((result, index) => ( +
+
+
+ + + {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. +

+
+ +
+
+ Text Color: + {result.textcolor} +
+
+ 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

+
+ )} +
+
+
+
+ ); +} diff --git a/app/batch/page.tsx b/app/batch/page.tsx index 4d0c45d..607982e 100644 --- a/app/batch/page.tsx +++ b/app/batch/page.tsx @@ -66,8 +66,10 @@ export default function BatchPage() { break; } - setOutputColors(result.colors); - toast.success(`Processed ${result.colors.length} colors`); + // Extract output colors from the result + const processed = result.colors.map((c) => c.output); + setOutputColors(processed); + toast.success(`Processed ${processed.length} colors`); } catch (error) { toast.error('Failed to process colors'); console.error(error); diff --git a/app/palettes/gradient/page.tsx b/app/palettes/gradient/page.tsx index 94d72fe..008dc4f 100644 --- a/app/palettes/gradient/page.tsx +++ b/app/palettes/gradient/page.tsx @@ -28,8 +28,8 @@ export default function GradientPage() { count, colorspace, }); - setGradient(result.colors); - toast.success(`Generated ${result.colors.length} colors`); + setGradient(result.gradient); + toast.success(`Generated ${result.gradient.length} colors`); } catch (error) { toast.error('Failed to generate gradient'); } @@ -156,7 +156,7 @@ export default function GradientPage() { {/* Preview */}
- {gradient.length > 0 && ( + {gradient && gradient.length > 0 && ( <>

Gradient Preview

diff --git a/app/palettes/harmony/page.tsx b/app/palettes/harmony/page.tsx index 8164a44..67d156d 100644 --- a/app/palettes/harmony/page.tsx +++ b/app/palettes/harmony/page.tsx @@ -6,7 +6,7 @@ import { PaletteGrid } from '@/components/color/PaletteGrid'; import { ExportMenu } from '@/components/tools/ExportMenu'; import { Button } from '@/components/ui/button'; import { Select } from '@/components/ui/select'; -import { useComplement, useRotate } from '@/lib/api/queries'; +import { useGeneratePalette } from '@/lib/api/queries'; import { Loader2, Palette } from 'lucide-react'; import { toast } from 'sonner'; @@ -23,83 +23,17 @@ export default function HarmonyPage() { const [harmonyType, setHarmonyType] = useState('complementary'); const [palette, setPalette] = useState([]); - const complementMutation = useComplement(); - const rotateMutation = useRotate(); + const paletteMutation = useGeneratePalette(); const generateHarmony = async () => { try { - let colors: string[] = [baseColor]; - - switch (harmonyType) { - case 'monochromatic': - // Base color with lightness variations - colors = [baseColor]; - toast.info('Monochromatic harmony uses variations of the base color'); - break; - - case 'analogous': - // Base + 30° and -30° - const analog1 = await rotateMutation.mutateAsync({ - colors: [baseColor], - amount: 30, - }); - const analog2 = await rotateMutation.mutateAsync({ - colors: [baseColor], - amount: -30, - }); - colors = [analog2.colors[0], baseColor, analog1.colors[0]]; - break; - - case 'complementary': - // Base + opposite (180°) - const complement = await complementMutation.mutateAsync([baseColor]); - colors = [baseColor, complement.colors[0]]; - break; - - case 'split-complementary': - // Base + 150° and 210° (flanking the complement) - const split1 = await rotateMutation.mutateAsync({ - colors: [baseColor], - amount: 150, - }); - const split2 = await rotateMutation.mutateAsync({ - colors: [baseColor], - amount: 210, - }); - colors = [baseColor, split1.colors[0], split2.colors[0]]; - break; - - case 'triadic': - // Base + 120° and 240° (evenly spaced) - const tri1 = await rotateMutation.mutateAsync({ - colors: [baseColor], - amount: 120, - }); - const tri2 = await rotateMutation.mutateAsync({ - colors: [baseColor], - amount: 240, - }); - colors = [baseColor, tri1.colors[0], tri2.colors[0]]; - break; - - case 'tetradic': - // Base + 90°, 180°, 270° (square) - const tet1 = await rotateMutation.mutateAsync({ - colors: [baseColor], - amount: 90, - }); - const tet2 = await rotateMutation.mutateAsync({ - colors: [baseColor], - amount: 180, - }); - const tet3 = await rotateMutation.mutateAsync({ - colors: [baseColor], - amount: 270, - }); - colors = [baseColor, tet1.colors[0], tet2.colors[0], tet3.colors[0]]; - break; - } + const result = await paletteMutation.mutateAsync({ + base: baseColor, + scheme: harmonyType, + }); + // Combine primary and secondary colors into flat array + const colors = [result.palette.primary, ...result.palette.secondary]; setPalette(colors); toast.success(`Generated ${harmonyType} harmony palette`); } catch (error) { @@ -157,10 +91,10 @@ export default function HarmonyPage() {