import React, { useState, useEffect, useRef } from 'react'; // Utility functions for color conversion and manipulation // HSL to Hex conversion (simplified for demonstration) const hslToHex = (h, s, l) => { l /= 100; const a = s * Math.min(l, 1 - l) / 100; const f = n => { const k = (n + h / 30) % 12; const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); return Math.round(255 * color).toString(16).padStart(2, '0'); }; return `#${f(0)}${f(8)}${f(4)}`; }; // Hex to HSL conversion (simplified for demonstration) const hexToHsl = (hex) => { let r = 0, g = 0, b = 0; // Handle short hex codes (e.g., #abc) if (hex.length === 4) { r = parseInt(hex[1] + hex[1], 16); g = parseInt(hex[2] + hex[2], 16); b = parseInt(hex[3] + hex[3], 16); } else if (hex.length === 7) { r = parseInt(hex.substring(1, 3), 16); g = parseInt(hex.substring(3, 5), 16); b = parseInt(hex.substring(5, 7), 16); } r /= 255; g /= 255; b /= 255; let cmin = Math.min(r, g, b), cmax = Math.max(r, g, b), delta = cmax - cmin, h = 0, s = 0, l = 0; if (delta === 0) h = 0; else if (cmax === r) h = ((g - b) / delta) % 6; else if (cmax === g) h = (b - r) / delta + 2; else h = (r - g) / delta + 4; h = Math.round(h * 60); if (h < 0) h += 360; l = (cmax + cmin) / 2; s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); s = +(s * 100).toFixed(1); l = +(l * 100).toFixed(1); return { h: h, s: s, l: l }; }; // Generates a random HSL color const getRandomHsl = () => ({ h: Math.floor(Math.random() * 360), s: Math.floor(Math.random() * 70) + 30, // 30-100% saturation l: Math.floor(Math.random() * 50) + 20, // 20-70% lightness }); // Applies a color harmony rule to a base color (simplified) const applyHarmonyRule = (baseHsl, rule) => { const palette = []; const { h, s, l } = baseHsl; switch (rule) { case 'monochromatic': // Generate variations of lightness and saturation palette.push(baseHsl); palette.push({ h, s: Math.max(0, s - 20), l: Math.min(100, l + 15) }); palette.push({ h, s: Math.min(100, s + 15), l: Math.max(0, l - 10) }); palette.push({ h, s: Math.max(0, s - 10), l: Math.min(100, l + 30) }); palette.push({ h, s: Math.min(100, s + 20), l: Math.max(0, l - 20) }); break; case 'complementary': palette.push(baseHsl); palette.push({ h: (h + 180) % 360, s, l }); // Complementary palette.push({ h: (h + 180) % 360, s: Math.max(0, s - 20), l: Math.min(100, l + 15) }); // Tint of complementary palette.push({ h, s: Math.max(0, s - 20), l: Math.min(100, l + 15) }); // Tint of base palette.push({ h: (h + 180) % 360, s: Math.min(100, s + 15), l: Math.max(0, l - 10) }); // Shade of complementary break; case 'analogous': palette.push(baseHsl); palette.push({ h: (h + 30) % 360, s, l }); // +30 degrees palette.push({ h: (h - 30 + 360) % 360, s, l }); // -30 degrees palette.push({ h: (h + 60) % 360, s: Math.min(100, s + 10), l: l }); palette.push({ h: (h - 60 + 360) % 360, s: Math.min(100, s + 10), l: l }); break; case 'triadic': palette.push(baseHsl); palette.push({ h: (h + 120) % 360, s, l }); palette.push({ h: (h + 240) % 360, s, l }); palette.push({ h: (h + 120) % 360, s: Math.max(0, s - 20), l: Math.min(100, l + 15) }); palette.push({ h: (h + 240) % 360, s: Math.min(100, s + 15), l: Math.max(0, l - 10) }); break; default: // Random return Array.from({ length: 5 }, getRandomHsl); } return palette.map(color => ({ ...color, hex: hslToHex(color.h, color.s, color.l) })); }; // Main App Component const App = () => { // State variables const [genreInput, setGenreInput] = useState(''); const [uploadedImage, setUploadedImage] = useState(null); const [colorPalette, setColorPalette] = useState([]); const [lockedColors, setLockedColors] = useState({}); // { index: hexColor, ... } const [harmonyRule, setHarmonyRule] = useState('random'); // monochromatic, complementary, analogous, triadic, random const [hslAdjustments, setHslAdjustments] = useState({ hue: 0, saturation: 0, lightness: 0 }); const [loading, setLoading] = useState(false); const fileInputRef = useRef(null); // Simulated AI for Text-to-Palette const genreToPaletteMap = { mystery: [ '#2c3e50', '#34495e', '#617d8a', '#95a5a6', '#c0392b' ], // Dark blues, grays, a hint of deep red fantasy: [ '#8e44ad', '#9b59b6', '#2980b9', '#3498db', '#f1c40f' ], // Purples, blues, gold sci_fi: [ '#1abc9c', '#16a085', '#2ecc71', '#27ae60', '#ecf0f1' ], // Teal, greens, light gray (futuristic) romance: [ '#e74c3c', '#c0392b', '#e67e22', '#f39c12', '#fce4ec' ], // Reds, oranges, light pink (warm) thriller: [ '#000000', '#333333', '#666666', '#bdbdbd', '#e74c3c' ], // Black, dark grays, stark red horror: [ '#0a0a0a', '#1a1a1a', '#4a0000', '#8a0000', '#bb0000' ], // Deep blacks, dark reds }; // Initial palette generation on component mount useEffect(() => { generatePalette('initial'); }, []); // Helper to generate a palette based on input const generatePalette = async (source = 'random', input = '') => { setLoading(true); let newPalette = []; let baseColorHsl = getRandomHsl(); // Incorporate locked colors first let currentLockedColors = Object.values(lockedColors).filter(Boolean).map(hexToHsl); if (currentLockedColors.length > 0) { baseColorHsl = currentLockedColors[0]; // Use first locked color as base } // --- Simulate AI Logic --- if (source === 'genre' && input) { const lowerInput = input.toLowerCase().replace(/\s/g, '_'); if (genreToPaletteMap[lowerInput]) { newPalette = genreToPaletteMap[lowerInput].map(hex => ({ ...hexToHsl(hex), hex })); } else { // If genre not found, generate a random one and show a message console.log(`Genre "${input}" not found in AI knowledge base. Generating random palette.`); newPalette = applyHarmonyRule(baseColorHsl, 'random'); } } else if (source === 'image' && uploadedImage) { newPalette = await extractColorsFromImage(uploadedImage); } else if (source === 'initial') { newPalette = applyHarmonyRule(baseColorHsl, 'random'); } else { // Default or random generation newPalette = applyHarmonyRule(baseColorHsl, harmonyRule); } // Apply locked colors to the new palette (replace slots or add if not enough) const finalPalette = []; for (let i = 0; i < 5; i++) { if (lockedColors[i]) { finalPalette.push({ ...hexToHsl(lockedColors[i]), hex: lockedColors[i] }); } else if (newPalette[i]) { finalPalette.push(newPalette[i]); } else { // If fewer colors from harmony rule, fill with random finalPalette.push(applyHarmonyRule(getRandomHsl(), 'random')[0]); } } setColorPalette(finalPalette); setLoading(false); }; // Client-side image color extraction (simplified) const extractColorsFromImage = (imageFile) => { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = (e) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, img.width, img.height); const pixels = ctx.getImageData(0, 0, img.width, img.height).data; const colorCounts = {}; // Sample every 100th pixel for performance for (let i = 0; i < pixels.length; i += 400) { const r = pixels[i]; const g = pixels[i + 1]; const b = pixels[i + 2]; const hex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; colorCounts[hex] = (colorCounts[hex] || 0) + 1; } // Sort by count to find dominant colors const sortedColors = Object.entries(colorCounts).sort(([, a], [, b]) => b - a); // Take top 5 dominant colors const dominantHexColors = sortedColors.slice(0, 5).map(([hex]) => hex); // Convert to HSL and resolve const palette = dominantHexColors.map(hex => ({ ...hexToHsl(hex), hex })); resolve(palette.length > 0 ? palette : Array.from({ length: 5 }, getRandomHsl)); // Fallback if no colors found }; img.src = e.target.result; }; reader.readAsDataURL(imageFile); }); }; // Handlers const handleGenreChange = (e) => setGenreInput(e.target.value); const handleImageUpload = (e) => { const file = e.target.files[0]; if (file) { setUploadedImage(file); generatePalette('image', file); // Generate palette from image } }; const handleGeneratePalette = () => { if (genreInput) { generatePalette('genre', genreInput); } else if (uploadedImage) { generatePalette('image', uploadedImage); } else { generatePalette('random'); } }; const toggleLockColor = (index) => { setLockedColors(prev => { const newLockedColors = { ...prev }; if (newLockedColors[index]) { delete newLockedColors[index]; // Unlock } else { newLockedColors[index] = colorPalette[index].hex; // Lock current color } return newLockedColors; }); }; const handleHslAdjust = (e, type, index) => { const value = parseInt(e.target.value, 10); const updatedPalette = [...colorPalette]; const targetColor = updatedPalette[index]; if (targetColor) { let { h, s, l } = targetColor; if (type === 'hue') h = (h + value) % 360; if (type === 'saturation') s = Math.max(0, Math.min(100, s + value)); if (type === 'lightness') l = Math.max(0, Math.min(100, l + value)); updatedPalette[index] = { h, s, l, hex: hslToHex(h, s, l) }; setColorPalette(updatedPalette); } }; // Function to apply HSL adjustments to a single color const applySingleHslAdjustment = (colorHsl, adjust) => { let { h, s, l } = colorHsl; h = (h + adjust.hue + 360) % 360; // Ensure hue stays within 0-359 s = Math.max(0, Math.min(100, s + adjust.saturation)); l = Math.max(0, Math.min(100, l + adjust.lightness)); return { h, s, l, hex: hslToHex(h, s, l) }; }; // Applies HSL adjustments to unlocked colors useEffect(() => { if (colorPalette.length > 0 && (hslAdjustments.hue !== 0 || hslAdjustments.saturation !== 0 || hslAdjustments.lightness !== 0)) { const adjustedPalette = colorPalette.map((color, index) => { if (lockedColors[index]) { return color; // Keep locked colors as is } return applySingleHslAdjustment(color, hslAdjustments); }); setColorPalette(adjustedPalette); setHslAdjustments({ hue: 0, saturation: 0, lightness: 0 }); // Reset adjustments after applying } }, [hslAdjustments.hue, hslAdjustments.saturation, hslAdjustments.lightness]); // Only run when manual HSL changes const handleHarmonyRuleChange = (rule) => { setHarmonyRule(rule); // Regenerate palette based on new rule. If there are locked colors, use one as base. if (Object.keys(lockedColors).length > 0) { const baseColorHex = Object.values(lockedColors)[0]; const baseHsl = hexToHsl(baseColorHex); const newPalette = applyHarmonyRule(baseHsl, rule); const finalPalette = newPalette.map((color, index) => lockedColors[index] ? { ...hexToHsl(lockedColors[index]), hex: lockedColors[index] } : color); setColorPalette(finalPalette); } else { generatePalette('harmony', rule); } }; // Helper for contrast checking (simplified) const getContrastTextColor = (hexColor) => { if (!hexColor) return '#000000'; // Default to black const hsl = hexToHsl(hexColor); return hsl.l > 50 ? '#000000' : '#FFFFFF'; // Black text for light colors, white for dark }; return (

AI Palette Generator

Generate harmonious color palettes from text genres or images, then fine-tune them to perfection.

{/* Input Section */}
fileInputRef.current.click()} > {uploadedImage ? ( Uploaded cover ) : ( )}

{uploadedImage ? uploadedImage.name : 'Click to upload or drag & drop'}


{/* Palette Display */}

Generated Color Palette

{loading && (

Generating palette...

)} {!loading && colorPalette.length > 0 && (
{colorPalette.map((color, index) => (
toggleLockColor(index)} > {/* Lock Button */} {/* Hex Code Display */} {color.hex.toUpperCase()} {/* HSL values for reference/debugging */} {/* H:{color.h}° S:{color.s}% L:{color.l}% */}
))}
)}

{/* Adjustment Tools */}

Fine-Tune Your Palette

{/* Color Harmony Rules */}

Color Harmony Rules (Applies to unlocked colors)

{['random', 'monochromatic', 'complementary', 'analogous', 'triadic'].map(rule => ( ))}
{/* HSL Adjustment Sliders (Applies to the whole palette for demonstration) */}

Adjust HSL (Applies to unlocked colors)

{['hue', 'saturation', 'lightness'].map((type) => (
setHslAdjustments(prev => ({ ...prev, [type]: parseInt(e.target.value) }))} onMouseUp={handleGeneratePalette} // Regenerate when done adjusting onTouchEnd={handleGeneratePalette} // For touch devices className="w-full h-2 bg-gray-300 dark:bg-gray-600 rounded-lg appearance-none cursor-pointer range-sm accent-blue-500" /> Delta: {hslAdjustments[type]}
))}

Note: HSL adjustments are applied to unlocked colors on release of the slider.

© {new Date().getFullYear()} AI Palette Generator. All rights reserved.

This app simulates AI functionality for demonstration purposes. Real AI models would involve complex backend processing.

Social Media: Facebook | Twitter | Newsletter: Subscribe

); }; export default App;

Weaving New Worlds With Words

Writing turns imagination into clear and vivid images with carefully chosen words. Each sentence skillfully creates unique places, memorable characters, and brings stories fully to life, transporting readers to captivating new worlds. By using rich details and genuine feelings, writers enable others to see fresh perspectives, experience a range of emotions, and gain a deeper understanding of what it means to be human—all conveyed powerfully through the art of words.