Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,53 @@ A powerful Next.js application for creating stunning SVG gradient backgrounds wi
## Features

- **Real-time Preview**: See your gradient backgrounds update instantly as you modify colors
- **Interactive Color Wheel**: Choose colors from an intuitive color wheel interface
- **Dual Color Selection**: Select two primary colors simultaneously on the same wheel
- **Free Selection Mode**: Customize both colors independently with full control
- **Recommended Selection Mode**: Get smart color combination recommendations based on color theory
- **Custom Color Palettes**: Add up to 8 colors to create unique gradients
- **Preset Templates**: Choose from professionally designed color combinations
- **API Integration**: Generate gradients programmatically via REST API
- **SVG Export**: Download your creations as high-quality SVG files
- **Responsive Design**: Works seamlessly on desktop and mobile devices

## Color Recommendation Algorithm

The recommended selection mode uses well-established color theory principles to suggest harmonious color combinations:

1. **Complementary Colors**: Colors opposite each other on the color wheel (high contrast, vibrant)
2. **Analogous Colors**: Colors adjacent to each other on the wheel (harmonious, cohesive)
3. **Triadic Colors**: Three colors equally spaced around the wheel (balanced, dynamic)
4. **Split Complementary**: A base color plus two colors adjacent to its complement (versatile, less jarring)
5. **Monochromatic**: Shades, tones, and tints of a single hue (elegant, minimalist)

The `getBestColorCombination` function intelligently selects the most appropriate scheme based on the primary color's saturation and value characteristics.

## Getting Started

Read the documentation at https://opennext.js.org/cloudflare.

## Usage

### Color Selection Modes

1. **Free Selection Mode**
- Drag the two markers on the color wheel to select any colors
- Click on either color preview box to activate it for editing
- Use the color picker or manual hex input for precise color control

2. **Recommended Selection Mode**
- Choose a primary color using the color picker or hex input
- Browse through 5 professionally designed color combination schemes
- Click on any recommendation to apply it instantly
- The "Best Combination" is automatically selected based on color theory

### Tips
- For vibrant colors, complementary colors work best
- For a more subtle look, try analogous or monochromatic schemes
- You can still add up to 6 additional colors for complex gradients
- Use the "Randomize" button to explore unexpected color combinations

## Develop

Run the Next.js development server:
Expand Down
4 changes: 2 additions & 2 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ export default nextConfig;

// Enable calling `getCloudflareContext()` in `next dev`.
// See https://opennext.js.org/cloudflare/bindings#local-access-to-bindings.
import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
initOpenNextCloudflareForDev();
// import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
// initOpenNextCloudflareForDev();
145 changes: 78 additions & 67 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Input } from '@/components/ui/input';
import { useGradientGenerator } from '@/hooks/useGradientGenerator';
import { colorPresets } from '@/lib/constants';
import { colorToParam } from '@/lib/utils';
import { ColorSelectorPanel } from '@/components/ColorSelectorPanel';
import { Download, RefreshCw, Plus, Trash2, Palette, Sparkles, Layers, Code, Zap } from 'lucide-react';
import { cn } from '@/lib/utils';

Expand Down Expand Up @@ -189,9 +190,9 @@ export default function GradientGenerator() {
type="number"
value={width}
onChange={(e) => setWidth(Number(e.target.value))}
className="font-mono"
className="font-mono pr-12"
/>
<span className="absolute right-3 top-2.5 text-xs text-muted-foreground">px</span>
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground pointer-events-none">px</span>
</div>
</div>
<div className="space-y-2">
Expand All @@ -201,83 +202,93 @@ export default function GradientGenerator() {
type="number"
value={height}
onChange={(e) => setHeight(Number(e.target.value))}
className="font-mono"
className="font-mono pr-12"
/>
<span className="absolute right-3 top-2.5 text-xs text-muted-foreground">px</span>
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground pointer-events-none">px</span>
</div>
</div>
</div>
</div>

{/* Colors */}
<div className="space-y-4">
<div className="flex items-center justify-between pb-2 border-b border-border">
<div className="flex items-center gap-2">
<Palette className="w-5 h-5 text-primary" />
<h2 className="font-display font-semibold text-lg">Colors</h2>
{/* New Color Selector */}
<ColorSelectorPanel
colors={colors}
onColorsChange={setColors}
/>

{/* Additional Colors (Optional) */}
{colors.length > 2 && (
<div className="space-y-4">
<div className="flex items-center justify-between pb-2 border-b border-border">
<div className="flex items-center gap-2">
<Palette className="w-5 h-5 text-primary" />
<h2 className="font-display font-semibold text-lg">Additional Colors</h2>
</div>
<span className="text-xs font-mono bg-muted px-2 py-1 rounded-md text-muted-foreground">
{colors.length}/8
</span>
</div>
<span className="text-xs font-mono bg-muted px-2 py-1 rounded-md text-muted-foreground">
{colors.length}/8
</span>
</div>

<div className="space-y-3 max-h-[300px] overflow-y-auto pr-2 custom-scrollbar">
{colors.map((color, index) => (
<div key={index} className="flex items-center gap-3 group">
<div className="relative flex-shrink-0">
<Input
type="color"
value={color}
onChange={(e) => handleColorChange(index, e.target.value)}
className="w-12 h-12 p-1 rounded-xl cursor-pointer border-2 hover:border-primary transition-colors"

<div className="space-y-3 max-h-[200px] overflow-y-auto pr-2 custom-scrollbar">
{colors.slice(2).map((color, index) => {
const actualIndex = index + 2;
return (
<div key={actualIndex} className="flex items-center gap-3 group">
<div className="relative flex-shrink-0">
<Input
type="color"
value={color}
onChange={(e) => handleColorChange(actualIndex, e.target.value)}
className="w-12 h-12 p-1 rounded-xl cursor-pointer border-2 hover:border-primary transition-colors"
/>
</div>
<Input
type="text"
value={color.toUpperCase()}
onChange={(e) => handleColorChange(actualIndex, e.target.value)}
className="font-mono text-sm tracking-wider uppercase"
/>
<Button
variant="ghost"
size="icon"
onClick={() => removeColor(actualIndex)}
className="text-muted-foreground hover:text-destructive hover:bg-destructive/10"
>
<Trash2 className="w-4 h-4" />
</Button>
</div>
);
})}
</div>

{colors.length < 8 && (
<div className="flex items-center gap-3 pt-2">
<div className="relative flex-shrink-0">
<Input
type="color"
value={newColor || '#000000'}
onChange={(e) => setNewColor(e.target.value)}
className="w-12 h-12 p-1 rounded-xl cursor-pointer border-2 border-dashed border-muted-foreground/30 hover:border-primary transition-colors"
/>
</div>
<Input
type="text"
placeholder="#000000"
value={newColor.toUpperCase()}
onChange={(e) => setNewColor(e.target.value)}
className="font-mono text-sm tracking-wider uppercase"
/>
</div>
<Input
type="text"
value={color.toUpperCase()}
onChange={(e) => handleColorChange(index, e.target.value)}
className="font-mono text-sm tracking-wider uppercase"
/>
<Button
variant="ghost"
size="icon"
onClick={() => removeColor(index)}
disabled={colors.length <= 1}
className="text-muted-foreground hover:text-destructive hover:bg-destructive/10"
<Button
onClick={addColor}
disabled={!newColor}
className="bg-secondary text-secondary-foreground hover:bg-secondary/80"
>
<Trash2 className="w-4 h-4" />
<Plus className="w-4 h-4" />
</Button>
</div>
))}
)}
</div>

{colors.length < 8 && (
<div className="flex items-center gap-3 pt-2">
<div className="relative flex-shrink-0">
<Input
type="color"
value={newColor || '#000000'}
onChange={(e) => setNewColor(e.target.value)}
className="w-12 h-12 p-1 rounded-xl cursor-pointer border-2 border-dashed border-muted-foreground/30 hover:border-primary transition-colors"
/>
</div>
<Input
type="text"
placeholder="#000000"
value={newColor.toUpperCase()}
onChange={(e) => setNewColor(e.target.value)}
className="font-mono text-sm tracking-wider uppercase"
/>
<Button
onClick={addColor}
disabled={!newColor}
className="bg-secondary text-secondary-foreground hover:bg-secondary/80"
>
<Plus className="w-4 h-4" />
</Button>
</div>
)}
</div>
)}

{/* Presets */}
<div className="space-y-4">
Expand Down