diff --git a/app/(app)/color/page.tsx b/app/(app)/color/page.tsx index 51e2ac9..a3ba8ea 100644 --- a/app/(app)/color/page.tsx +++ b/app/(app)/color/page.tsx @@ -13,6 +13,7 @@ import { useColorInfo, useGeneratePalette, useGenerateGradient } from '@/lib/col import { Loader2, Share2, Palette, Plus, X, Layers } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; import { Select, SelectContent, @@ -137,37 +138,36 @@ function PlaygroundContent() { title="Color" description="Interactive color manipulation and analysis tool" > -
+
{/* Row 1: Workspace */} -
+
{/* Main Workspace: Color Picker and Information */}
Color Picker - -
+
- -
-

Color Information

+ +
{isLoading && (
- +
)} {isError && ( -
+

Error loading color information

-

{error?.message || 'Unknown error'}

+

{error?.message || 'Unknown error'}

)} @@ -182,7 +182,7 @@ function PlaygroundContent() {
- Color Manipulation + Adjustments @@ -192,15 +192,12 @@ function PlaygroundContent() {
{/* Row 2: Harmony Generator */} -
+
{/* Harmony Controls */}
- - - Harmony Type - + Harmony -

+

{harmonyDescriptions[harmonyType]}

@@ -230,14 +227,11 @@ function PlaygroundContent() { > {paletteMutation.isPending ? ( <> - - Generating.. + + Generating... ) : ( - <> - - Generate Harmony - + 'Generate' )}
@@ -249,21 +243,21 @@ function PlaygroundContent() { - Generated Palette {palette.length > 0 && `(${palette.length} colors)`} + Palette {palette.length > 0 && ({palette.length})} {palette.length > 0 ? ( -
+
-
+
) : ( -
- -

Select a harmony type and click Generate to create your palette based on the current color

+
+ +

Generate a harmony palette from the current color

)} @@ -272,74 +266,72 @@ function PlaygroundContent() {
{/* Row 3: Gradient Generator */} -
+
{/* Gradient Controls */}
- - - Gradient Controls - + Gradient - -
-
-

Color Stops

- {stops.map((stop, index) => ( -
-
- updateStop(index, e.target.value)} - className="h-10 w-full cursor-pointer p-1" - /> -
- {index !== 0 && stops.length > 2 && ( - - )} -
- ))} - -
- -
-

Steps

- setGradientCount(parseInt(e.target.value))} - /> -
- - + )} +
+ ))} +
+ +
+ + setGradientCount(parseInt(e.target.value))} + /> +
+ +
@@ -349,27 +341,27 @@ function PlaygroundContent() { - Generated Gradient {gradientResult.length > 0 && `(${gradientResult.length} colors)`} + Gradient {gradientResult.length > 0 && ({gradientResult.length})} {gradientResult.length > 0 ? ( -
+
-
+
) : ( -
- -

Add color stops and click Generate to create your smooth gradient

+
+ +

Add color stops and generate a smooth gradient

)} diff --git a/app/(app)/favicon/page.tsx b/app/(app)/favicon/page.tsx index f337577..d4a2b7e 100644 --- a/app/(app)/favicon/page.tsx +++ b/app/(app)/favicon/page.tsx @@ -3,7 +3,6 @@ import * as React from 'react'; import { AppPage } from '@/components/layout/AppPage'; import { FaviconGenerator } from '@/components/favicon/FaviconGenerator'; -import { Globe, Shield, Zap } from 'lucide-react'; export default function FaviconPage() { return ( @@ -11,9 +10,7 @@ export default function FaviconPage() { title="Favicon Generator" description="Create a complete set of icons for your website including PWA manifest and HTML code." > -
- -
+ ); } diff --git a/app/page.tsx b/app/page.tsx index 51b0fe5..f591f49 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -9,7 +9,7 @@ import BackToTop from '@/components/BackToTop'; export default function Home() { return ( -
+
diff --git a/components/ascii/ASCIIConverter.tsx b/components/ascii/ASCIIConverter.tsx index 6ba3c6c..7cf896b 100644 --- a/components/ascii/ASCIIConverter.tsx +++ b/components/ascii/ASCIIConverter.tsx @@ -11,7 +11,6 @@ import { addRecentFont } from '@/lib/storage/favorites'; import { decodeFromUrl, updateUrl, getShareableUrl } from '@/lib/utils/urlSharing'; import { toast } from 'sonner'; import type { ASCIIFont } from '@/types/ascii'; -import { Car } from 'lucide-react'; import { Card, CardContent } from '../ui/card'; export function ASCIIConverter() { diff --git a/components/ascii/FontPreview.tsx b/components/ascii/FontPreview.tsx index a937d98..0a16dfa 100644 --- a/components/ascii/FontPreview.tsx +++ b/components/ascii/FontPreview.tsx @@ -61,111 +61,95 @@ export function FontPreview({ text, font, isLoading, onCopy, onDownload, onShare
Preview {font && ( - + {font} )}
-
+
{onCopy && ( - )} {onShare && ( - )} - {onDownload && ( - )}
- + {/* Controls */}
-
+
-
- - - +
+ {(['xs', 'sm', 'base'] as const).map((s) => ( + + ))}
+ + {!isLoading && text && ( +
+ {lineCount} lines + {charCount} chars +
+ )}
- {!isLoading && text && ( -
- {lineCount} lines - - {charCount} chars -
- )} -
- Select Font + Fonts {onRandomFont && ( )} @@ -112,34 +112,34 @@ export function FontSelector({ setFilter(v as FilterType)} - className="mb-4 shrink-0" + className="mb-3 shrink-0" > - + All - - Favorites + + Fav - + Recent {/* Search Input */} -
- +
+ setSearchQuery(e.target.value)} - className="pl-9 pr-9" + className="pl-8 pr-8 h-8 text-sm" /> {searchQuery && ( )}
{/* Font List */} -
+
{filteredFonts.length === 0 ? ( @@ -185,26 +185,26 @@ export function FontSelector({
@@ -214,10 +214,10 @@ export function FontSelector({
{/* Stats */} -
+
{filteredFonts.length} font{filteredFonts.length !== 1 ? 's' : ''} - {filter === 'favorites' && ` • ${favorites.length} total favorites`} - {filter === 'recent' && ` • ${recentFonts.length} recent`} + {filter === 'favorites' && ` · ${favorites.length} favorites`} + {filter === 'recent' && ` · ${recentFonts.length} recent`}
diff --git a/components/color/ColorInfo.tsx b/components/color/ColorInfo.tsx index 97ab842..3a26585 100644 --- a/components/color/ColorInfo.tsx +++ b/components/color/ColorInfo.tsx @@ -48,48 +48,43 @@ export function ColorInfo({ info, className }: ColorInfoProps) { ]; return ( -
-
+
+
{formats.map((format) => (
-
-
{format.label}
-
{format.value}
+
+ {format.label} + {format.value}
))}
-
-
-
Brightness
-
{(info.brightness * 100).toFixed(1)}%
+
+
+
Brightness
+
{(info.brightness * 100).toFixed(1)}%
-
-
Luminance
-
{(info.luminance * 100).toFixed(1)}%
+
+
Luminance
+
{(info.luminance * 100).toFixed(1)}%
-
-
Type
-
{info.is_light ? 'Light' : 'Dark'}
+
+
{info.name && typeof info.name === 'string' ? 'Name' : 'Type'}
+
{info.name && typeof info.name === 'string' ? info.name : (info.is_light ? 'Light' : 'Dark')}
- {info.name && typeof info.name === 'string' && ( -
-
Named
-
{info.name}
-
- )}
); diff --git a/components/color/ColorPicker.tsx b/components/color/ColorPicker.tsx index 94a123a..a0147df 100644 --- a/components/color/ColorPicker.tsx +++ b/components/color/ColorPicker.tsx @@ -2,6 +2,7 @@ import { HexColorPicker } from 'react-colorful'; import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; import { cn } from '@/lib/utils/cn'; import { hexToRgb } from '@/lib/color/utils/color'; @@ -29,20 +30,20 @@ export function ColorPicker({ color, onChange, className }: ColorPickerProps) { const textColor = getContrastColor(color); return ( -
-
+
+
-
- +
+ -
-
-
-

Export Format

- -
+
+
+ -
-

Color Space

- -
+
-
+
{isConverting ? (
- +
) : null} -
+          
             {getExportContent()}
           
-
- -
diff --git a/components/color/ManipulationPanel.tsx b/components/color/ManipulationPanel.tsx index f173ba8..e985ff2 100644 --- a/components/color/ManipulationPanel.tsx +++ b/components/color/ManipulationPanel.tsx @@ -12,12 +12,25 @@ import { useComplement } from '@/lib/color/api/queries'; import { toast } from 'sonner'; +import { Sun, Moon, Droplets, Droplet, RotateCcw, ArrowLeftRight } from 'lucide-react'; interface ManipulationPanelProps { color: string; onColorChange: (color: string) => void; } +interface ManipulationRow { + label: string; + icon: React.ReactNode; + value: number; + setValue: (v: number) => void; + format: (v: number) => string; + min: number; + max: number; + step: number; + onApply: () => Promise; +} + export function ManipulationPanel({ color, onColorChange }: ManipulationPanelProps) { const [lightenAmount, setLightenAmount] = useState(0.2); const [darkenAmount, setDarkenAmount] = useState(0.2); @@ -32,93 +45,6 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro const rotateMutation = useRotate(); const complementMutation = useComplement(); - const handleLighten = async () => { - try { - const result = await lightenMutation.mutateAsync({ - colors: [color], - amount: lightenAmount, - }); - if (result.colors[0]) { - onColorChange(result.colors[0].output); - toast.success(`Lightened by ${(lightenAmount * 100).toFixed(0)}%`); - } - } catch (error) { - toast.error('Failed to lighten color'); - } - }; - - const handleDarken = async () => { - try { - const result = await darkenMutation.mutateAsync({ - colors: [color], - amount: darkenAmount, - }); - if (result.colors[0]) { - onColorChange(result.colors[0].output); - toast.success(`Darkened by ${(darkenAmount * 100).toFixed(0)}%`); - } - } catch (error) { - toast.error('Failed to darken color'); - } - }; - - const handleSaturate = async () => { - try { - const result = await saturateMutation.mutateAsync({ - colors: [color], - amount: saturateAmount, - }); - if (result.colors[0]) { - onColorChange(result.colors[0].output); - toast.success(`Saturated by ${(saturateAmount * 100).toFixed(0)}%`); - } - } catch (error) { - toast.error('Failed to saturate color'); - } - }; - - const handleDesaturate = async () => { - try { - const result = await desaturateMutation.mutateAsync({ - colors: [color], - amount: desaturateAmount, - }); - if (result.colors[0]) { - onColorChange(result.colors[0].output); - toast.success(`Desaturated by ${(desaturateAmount * 100).toFixed(0)}%`); - } - } catch (error) { - toast.error('Failed to desaturate color'); - } - }; - - const handleRotate = async () => { - try { - const result = await rotateMutation.mutateAsync({ - colors: [color], - amount: rotateAmount, - }); - if (result.colors[0]) { - onColorChange(result.colors[0].output); - toast.success(`Rotated hue by ${rotateAmount}°`); - } - } catch (error) { - toast.error('Failed to rotate hue'); - } - }; - - const handleComplement = async () => { - try { - const result = await complementMutation.mutateAsync([color]); - if (result.colors[0]) { - onColorChange(result.colors[0].output); - toast.success('Generated complementary color'); - } - } catch (error) { - toast.error('Failed to generate complement'); - } - }; - const isLoading = lightenMutation.isPending || darkenMutation.isPending || @@ -127,108 +53,151 @@ export function ManipulationPanel({ color, onColorChange }: ManipulationPanelPro rotateMutation.isPending || complementMutation.isPending; + const handleMutation = async ( + mutationFn: (params: any) => Promise, + params: any, + successMsg: string, + errorMsg: string + ) => { + try { + const result = await mutationFn(params); + if (result.colors[0]) { + onColorChange(result.colors[0].output); + toast.success(successMsg); + } + } catch { + toast.error(errorMsg); + } + }; + + const rows: ManipulationRow[] = [ + { + label: 'Lighten', + icon: , + value: lightenAmount, + setValue: setLightenAmount, + format: (v) => `${(v * 100).toFixed(0)}%`, + min: 0, max: 1, step: 0.05, + onApply: () => handleMutation( + lightenMutation.mutateAsync, + { colors: [color], amount: lightenAmount }, + `Lightened by ${(lightenAmount * 100).toFixed(0)}%`, + 'Failed to lighten color' + ), + }, + { + label: 'Darken', + icon: , + value: darkenAmount, + setValue: setDarkenAmount, + format: (v) => `${(v * 100).toFixed(0)}%`, + min: 0, max: 1, step: 0.05, + onApply: () => handleMutation( + darkenMutation.mutateAsync, + { colors: [color], amount: darkenAmount }, + `Darkened by ${(darkenAmount * 100).toFixed(0)}%`, + 'Failed to darken color' + ), + }, + { + label: 'Saturate', + icon: , + value: saturateAmount, + setValue: setSaturateAmount, + format: (v) => `${(v * 100).toFixed(0)}%`, + min: 0, max: 1, step: 0.05, + onApply: () => handleMutation( + saturateMutation.mutateAsync, + { colors: [color], amount: saturateAmount }, + `Saturated by ${(saturateAmount * 100).toFixed(0)}%`, + 'Failed to saturate color' + ), + }, + { + label: 'Desaturate', + icon: , + value: desaturateAmount, + setValue: setDesaturateAmount, + format: (v) => `${(v * 100).toFixed(0)}%`, + min: 0, max: 1, step: 0.05, + onApply: () => handleMutation( + desaturateMutation.mutateAsync, + { colors: [color], amount: desaturateAmount }, + `Desaturated by ${(desaturateAmount * 100).toFixed(0)}%`, + 'Failed to desaturate color' + ), + }, + { + label: 'Rotate', + icon: , + value: rotateAmount, + setValue: setRotateAmount, + format: (v) => `${v}°`, + min: -180, max: 180, step: 5, + onApply: () => handleMutation( + rotateMutation.mutateAsync, + { colors: [color], amount: rotateAmount }, + `Rotated hue by ${rotateAmount}°`, + 'Failed to rotate hue' + ), + }, + ]; + + const handleComplement = async () => { + try { + const result = await complementMutation.mutateAsync([color]); + if (result.colors[0]) { + onColorChange(result.colors[0].output); + toast.success('Generated complementary color'); + } + } catch { + toast.error('Failed to generate complement'); + } + }; + return ( -
- {/* Lighten */} -
-
- - {(lightenAmount * 100).toFixed(0)}% +
+ {rows.map((row) => ( +
+
+
+ {row.icon} + {row.label} +
+ {row.format(row.value)} +
+
+ row.setValue(vals[0])} + className="flex-1" + /> + +
- setLightenAmount(vals[0])} - /> - -
+ ))} - {/* Darken */} -
-
- - {(darkenAmount * 100).toFixed(0)}% -
- setDarkenAmount(vals[0])} - /> - -
- - {/* Saturate */} -
-
- - {(saturateAmount * 100).toFixed(0)}% -
- setSaturateAmount(vals[0])} - /> - -
- - {/* Desaturate */} -
-
- - {(desaturateAmount * 100).toFixed(0)}% -
- setDesaturateAmount(vals[0])} - /> - -
- - {/* Rotate Hue */} -
-
- - {rotateAmount}° -
- setRotateAmount(vals[0])} - /> - -
- - {/* Quick Actions */} -
-

Quick Actions

+
diff --git a/components/favicon/FaviconFileUpload.tsx b/components/favicon/FaviconFileUpload.tsx index f191958..c558f5b 100644 --- a/components/favicon/FaviconFileUpload.tsx +++ b/components/favicon/FaviconFileUpload.tsx @@ -88,14 +88,14 @@ export function FaviconFileUpload({ /> {selectedFile ? ( -
-
-
- +
+
+
+
-

+

{selectedFile.name}

-
-
+
+
{(selectedFile.size / 1024).toFixed(1)} KB
{dimensions && ( -
+
{dimensions}
@@ -131,23 +131,23 @@ export function FaviconFileUpload({ onDragLeave={handleDragLeave} onDrop={handleDrop} className={cn( - 'border-2 border-dashed rounded-xl p-10 text-center cursor-pointer transition-all duration-300', - 'hover:border-primary/50 hover:bg-primary/5', + 'border-2 border-dashed rounded-xl p-8 text-center cursor-pointer transition-all duration-200', + 'hover:border-primary/40 hover:bg-primary/5', { 'border-primary bg-primary/10 scale-[0.98]': isDragging, - 'border-border bg-muted/30': !isDragging, + 'border-border/50': !isDragging, 'opacity-50 cursor-not-allowed': disabled, } )} > -
- +
+
-

+

Drop icon source here

-

- Recommended: 512x512 PNG or SVG +

+ 512x512 PNG or SVG recommended

)} diff --git a/components/favicon/FaviconGenerator.tsx b/components/favicon/FaviconGenerator.tsx index 5f3e355..96e40df 100644 --- a/components/favicon/FaviconGenerator.tsx +++ b/components/favicon/FaviconGenerator.tsx @@ -1,9 +1,9 @@ 'use client'; import * as React from 'react'; -import { Download, Loader2, RefreshCw, Code2, Globe, Layout, Palette } from 'lucide-react'; +import { Download, Loader2, Code2, Globe, Layout } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Progress } from '@/components/ui/progress'; @@ -76,22 +76,16 @@ export function FaviconGenerator() { }; return ( -
+
{/* Settings Column */}
- + - - - App Details - - - Configure how your app appears on devices - + App Details - -
- + +
+
-
- +
+ - + - - - Theme Colors - + Theme Colors - -
-
- + +
+
+
setOptions({ ...options, backgroundColor: e.target.value })} /> @@ -138,13 +129,13 @@ export function FaviconGenerator() { />
-
- +
+
setOptions({ ...options, themeColor: e.target.value })} /> @@ -159,8 +150,8 @@ export function FaviconGenerator() { - - + + {result && ( @@ -200,58 +187,51 @@ export function FaviconGenerator() { {/* Results Column */}
- {!result && !isGenerating ? ( - - -

Ready to generate

-

Upload a square image (PNG or SVG recommended) and configure your app details to get started.

-
- ) : isGenerating ? ( - -
-
- -
-
-
- Processing Icons - {progress}% + {isGenerating ? ( + + +
+
+
+ Processing... +
+ {progress}%
- +
- ) : ( -
+ ) : result ? ( +
-

Generated Assets

-
- - + + Icons - - - HTML Code + + + HTML - - + + Manifest - -
+ +
{result?.icons.map((icon) => ( - -
-
+ +
+
{icon.previewUrl && ( )}
-
-

+

+

{icon.name}

- {icon.width}x{icon.height} • {(icon.size / 1024).toFixed(1)} KB + {icon.width}x{icon.height} · {(icon.size / 1024).toFixed(1)} KB

@@ -274,27 +254,27 @@ export function FaviconGenerator() {
- -
- + +
+ {result && }
-
-

- Note: Make sure to place the generated files in your website's root directory or update the href paths accordingly. +

+

+ Place generated files in your site root or update the href paths.

- -
- + +
+ {result && }
- )} + ) : null}
); diff --git a/components/layout/AppHeader.tsx b/components/layout/AppHeader.tsx index a0594ad..d459ed6 100644 --- a/components/layout/AppHeader.tsx +++ b/components/layout/AppHeader.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; -import { Menu, Search, Bell, ChevronRight, Moon, Sun, X } from 'lucide-react'; +import { Menu, ChevronRight, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils/cn'; import { useSidebar } from './SidebarProvider'; @@ -17,7 +17,7 @@ export function AppHeader() { const pathSegments = pathname.split('/').filter(Boolean); return ( -
+