|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>ChromaGen - Advanced Color Generator</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/color-thief/2.3.0/color-thief.umd.js"></script> |
|
<style> |
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(10px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
|
|
.color-card { |
|
animation: fadeIn 0.3s ease-out forwards; |
|
opacity: 0; |
|
} |
|
|
|
.color-card:nth-child(1) { animation-delay: 0.1s; } |
|
.color-card:nth-child(2) { animation-delay: 0.2s; } |
|
.color-card:nth-child(3) { animation-delay: 0.3s; } |
|
.color-card:nth-child(4) { animation-delay: 0.4s; } |
|
.color-card:nth-child(5) { animation-delay: 0.5s; } |
|
|
|
.gradient-bg { |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
} |
|
|
|
.image-preview { |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.image-preview:hover { |
|
transform: scale(1.02); |
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); |
|
} |
|
|
|
.toggle-checkbox:checked { |
|
right: 0; |
|
background-color: #4f46e5; |
|
} |
|
|
|
.toggle-checkbox:checked + .toggle-label { |
|
background-color: #4f46e5; |
|
} |
|
|
|
.color-swatch { |
|
transition: all 0.2s ease; |
|
} |
|
|
|
.color-swatch:hover { |
|
transform: scale(1.05); |
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); |
|
z-index: 10; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200 transition-colors duration-300"> |
|
<div class="min-h-screen flex flex-col"> |
|
|
|
<header class="gradient-bg text-white py-6 shadow-lg"> |
|
<div class="container mx-auto px-4"> |
|
<div class="flex justify-between items-center"> |
|
<h1 class="text-3xl font-bold tracking-tight"> |
|
<i class="fas fa-palette mr-2"></i> ChromaGen |
|
</h1> |
|
<div class="flex items-center space-x-4"> |
|
<div class="flex items-center"> |
|
<span class="mr-2"><i class="fas fa-moon"></i></span> |
|
<div class="relative inline-block w-12 mr-2 align-middle select-none"> |
|
<input type="checkbox" id="toggle" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer transition-transform duration-200 ease-in-out" checked/> |
|
<label for="toggle" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer transition-colors duration-200 ease-in-out w-12"></label> |
|
</div> |
|
<span><i class="fas fa-sun"></i></span> |
|
</div> |
|
<button id="info-btn" class="p-2 rounded-full hover:bg-white hover:bg-opacity-20 transition-colors"> |
|
<i class="fas fa-info-circle text-xl"></i> |
|
</button> |
|
</div> |
|
</div> |
|
<p class="mt-2 opacity-90">Advanced color palette generator with image extraction</p> |
|
</div> |
|
</header> |
|
|
|
|
|
<main class="flex-grow container mx-auto px-4 py-8"> |
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> |
|
|
|
<div class="lg:col-span-1 bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6"> |
|
<h2 class="text-xl font-semibold mb-4 flex items-center"> |
|
<i class="fas fa-sliders-h mr-2"></i> Controls |
|
</h2> |
|
|
|
<div class="space-y-6"> |
|
|
|
<div> |
|
<label class="block text-sm font-medium mb-2">Color Category</label> |
|
<select id="color-category" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"> |
|
<option value="any">Any Color</option> |
|
<option value="warm">Warm Colors</option> |
|
<option value="cool">Cool Colors</option> |
|
<option value="pastel">Pastel Colors</option> |
|
<option value="vibrant">Vibrant Colors</option> |
|
<option value="monochrome">Monochrome</option> |
|
<option value="analogous">Analogous</option> |
|
<option value="complementary">Complementary</option> |
|
<option value="triadic">Triadic</option> |
|
</select> |
|
</div> |
|
|
|
|
|
<div> |
|
<label class="block text-sm font-medium mb-2">Number of Colors</label> |
|
<input id="color-count" type="range" min="1" max="12" value="5" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"> |
|
<div class="flex justify-between text-xs text-gray-500 dark:text-gray-400 mt-1"> |
|
<span>1</span> |
|
<span>12</span> |
|
</div> |
|
<div class="text-center mt-1"> |
|
<span id="count-display" class="font-medium">5</span> colors |
|
</div> |
|
</div> |
|
|
|
|
|
<button id="generate-btn" class="w-full py-3 px-4 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg shadow-md transition duration-300 flex items-center justify-center"> |
|
<i class="fas fa-random mr-2"></i> Generate Colors |
|
</button> |
|
|
|
|
|
<div class="relative"> |
|
<div class="absolute inset-0 flex items-center"> |
|
<div class="w-full border-t border-gray-300 dark:border-gray-600"></div> |
|
</div> |
|
<div class="relative flex justify-center"> |
|
<span class="px-2 bg-white dark:bg-gray-800 text-sm text-gray-500">OR</span> |
|
</div> |
|
</div> |
|
|
|
|
|
<div> |
|
<label class="block text-sm font-medium mb-2">Extract from Image</label> |
|
<div class="flex space-x-2"> |
|
<input id="image-url" type="text" placeholder="Image URL" class="flex-grow p-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"> |
|
<button id="load-url-btn" class="p-2 bg-gray-200 dark:bg-gray-700 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition"> |
|
<i class="fas fa-link"></i> |
|
</button> |
|
</div> |
|
<div class="mt-2"> |
|
<label for="file-upload" class="cursor-pointer w-full py-2 px-4 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition flex items-center justify-center"> |
|
<i class="fas fa-folder-open mr-2"></i> Browse File |
|
</label> |
|
<input id="file-upload" type="file" accept="image/*" class="hidden"> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="image-preview-container" class="hidden"> |
|
<label class="block text-sm font-medium mb-2">Image Preview</label> |
|
<div class="image-preview relative overflow-hidden rounded-lg border border-gray-300 dark:border-gray-600"> |
|
<img id="preview-image" src="" alt="Preview" class="w-full h-auto max-h-48 object-cover"> |
|
<button id="clear-image" class="absolute top-2 right-2 bg-black bg-opacity-50 text-white p-1 rounded-full hover:bg-opacity-70 transition"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
<div class="mt-2 flex justify-between"> |
|
<span id="extract-status" class="text-sm text-gray-500 dark:text-gray-400"></span> |
|
<button id="extract-colors-btn" class="text-sm py-1 px-3 bg-indigo-600 hover:bg-indigo-700 text-white rounded transition"> |
|
Extract Colors |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="lg:col-span-2"> |
|
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h2 class="text-xl font-semibold flex items-center"> |
|
<i class="fas fa-swatchbook mr-2"></i> Color Palette |
|
</h2> |
|
<div class="flex space-x-2"> |
|
<button id="copy-all-btn" class="p-2 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition" title="Copy all colors"> |
|
<i class="fas fa-copy"></i> |
|
</button> |
|
<button id="save-palette-btn" class="p-2 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition" title="Save palette"> |
|
<i class="fas fa-save"></i> |
|
</button> |
|
<button id="export-svg-btn" class="p-2 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition" title="Export as SVG"> |
|
<i class="fas fa-file-export"></i> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<div id="color-results" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-4"> |
|
|
|
<div class="color-card"> |
|
<div class="color-swatch h-24 rounded-lg shadow-md mb-2" style="background-color: #4f46e5;"></div> |
|
<div class="text-center"> |
|
<div class="font-mono text-sm">#4f46e5</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">RGB(79, 70, 229)</div> |
|
</div> |
|
</div> |
|
<div class="color-card"> |
|
<div class="color-swatch h-24 rounded-lg shadow-md mb-2" style="background-color: #10b981;"></div> |
|
<div class="text-center"> |
|
<div class="font-mono text-sm">#10b981</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">RGB(16, 185, 129)</div> |
|
</div> |
|
</div> |
|
<div class="color-card"> |
|
<div class="color-swatch h-24 rounded-lg shadow-md mb-2" style="background-color: #f59e0b;"></div> |
|
<div class="text-center"> |
|
<div class="font-mono text-sm">#f59e0b</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">RGB(245, 158, 11)</div> |
|
</div> |
|
</div> |
|
<div class="color-card"> |
|
<div class="color-swatch h-24 rounded-lg shadow-md mb-2" style="background-color: #ef4444;"></div> |
|
<div class="text-center"> |
|
<div class="font-mono text-sm">#ef4444</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">RGB(239, 68, 68)</div> |
|
</div> |
|
</div> |
|
<div class="color-card"> |
|
<div class="color-swatch h-24 rounded-lg shadow-md mb-2" style="background-color: #8b5cf6;"></div> |
|
<div class="text-center"> |
|
<div class="font-mono text-sm">#8b5cf6</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">RGB(139, 92, 246)</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="color-details" class="mt-6 bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 hidden"> |
|
<h2 class="text-xl font-semibold mb-4 flex items-center"> |
|
<i class="fas fa-info-circle mr-2"></i> Color Details |
|
</h2> |
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|
<div> |
|
<div class="flex items-center mb-4"> |
|
<div id="detail-swatch" class="w-16 h-16 rounded-lg shadow-md mr-4"></div> |
|
<div> |
|
<div id="detail-hex" class="font-mono text-lg font-bold"></div> |
|
<div id="detail-rgb" class="text-sm text-gray-500 dark:text-gray-400"></div> |
|
</div> |
|
</div> |
|
<div class="space-y-2"> |
|
<div class="flex justify-between"> |
|
<span>HSL:</span> |
|
<span id="detail-hsl" class="font-mono"></span> |
|
</div> |
|
<div class="flex justify-between"> |
|
<span>HSV:</span> |
|
<span id="detail-hsv" class="font-mono"></span> |
|
</div> |
|
<div class="flex justify-between"> |
|
<span>CMYK:</span> |
|
<span id="detail-cmyk" class="font-mono"></span> |
|
</div> |
|
</div> |
|
</div> |
|
<div> |
|
<h3 class="font-medium mb-2">Color Variations</h3> |
|
<div class="grid grid-cols-5 gap-2"> |
|
<div class="flex flex-col items-center"> |
|
<div class="w-full h-8 rounded shadow-inner" id="lighten-20"></div> |
|
<span class="text-xs mt-1">+20%</span> |
|
</div> |
|
<div class="flex flex-col items-center"> |
|
<div class="w-full h-8 rounded shadow-inner" id="lighten-10"></div> |
|
<span class="text-xs mt-1">+10%</span> |
|
</div> |
|
<div class="flex flex-col items-center"> |
|
<div class="w-full h-8 rounded shadow-inner border-2 border-white dark:border-gray-800 shadow-lg" id="base-color"></div> |
|
<span class="text-xs mt-1">Base</span> |
|
</div> |
|
<div class="flex flex-col items-center"> |
|
<div class="w-full h-8 rounded shadow-inner" id="darken-10"></div> |
|
<span class="text-xs mt-1">-10%</span> |
|
</div> |
|
<div class="flex flex-col items-center"> |
|
<div class="w-full h-8 rounded shadow-inner" id="darken-20"></div> |
|
<span class="text-xs mt-1">-20%</span> |
|
</div> |
|
</div> |
|
|
|
<h3 class="font-medium mt-4 mb-2">Accessibility</h3> |
|
<div class="flex items-center justify-between p-2 rounded bg-gray-100 dark:bg-gray-700"> |
|
<span>AA Normal Text:</span> |
|
<span id="aa-normal" class="font-medium"></span> |
|
</div> |
|
<div class="flex items-center justify-between p-2 rounded mt-1"> |
|
<span>AA Large Text:</span> |
|
<span id="aa-large" class="font-medium"></span> |
|
</div> |
|
<div class="flex items-center justify-between p-2 rounded bg-gray-100 dark:bg-gray-700 mt-1"> |
|
<span>AAA Normal Text:</span> |
|
<span id="aaa-normal" class="font-medium"></span> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</main> |
|
|
|
|
|
<footer class="bg-gray-200 dark:bg-gray-800 py-4"> |
|
<div class="container mx-auto px-4 text-center text-sm text-gray-600 dark:text-gray-400"> |
|
<p>ChromaGen © 2023 | Advanced Color Palette Generator</p> |
|
</div> |
|
</footer> |
|
</div> |
|
|
|
|
|
<div id="info-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
|
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-xl p-6 max-w-lg w-full mx-4 max-h-[90vh] overflow-y-auto"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-xl font-semibold">About ChromaGen</h3> |
|
<button id="close-modal" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
<div class="space-y-4"> |
|
<p>ChromaGen is an advanced color palette generator that helps designers and developers create beautiful color schemes.</p> |
|
<h4 class="font-medium">Features:</h4> |
|
<ul class="list-disc pl-5 space-y-1"> |
|
<li>Generate random color palettes based on different categories</li> |
|
<li>Extract color palettes from images (URL or file upload)</li> |
|
<li>View detailed color information including RGB, HSL, HSV, CMYK</li> |
|
<li>See color variations (lighten/darken)</li> |
|
<li>Check color accessibility ratings</li> |
|
<li>Copy colors or export palettes</li> |
|
<li>Dark/light mode toggle</li> |
|
</ul> |
|
<h4 class="font-medium">How to use:</h4> |
|
<ol class="list-decimal pl-5 space-y-1"> |
|
<li>Select a color category or use "Any Color"</li> |
|
<li>Choose how many colors you want in your palette</li> |
|
<li>Click "Generate Colors" or upload an image to extract colors</li> |
|
<li>Click on any color to see detailed information</li> |
|
<li>Use the copy buttons to save colors to your clipboard</li> |
|
</ol> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="toast" class="fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg flex items-center transform translate-y-10 opacity-0 transition-all duration-300 z-50"> |
|
<i class="fas fa-check-circle mr-2"></i> |
|
<span id="toast-message">Copied to clipboard!</span> |
|
</div> |
|
|
|
<script> |
|
|
|
const toggle = document.getElementById('toggle'); |
|
const generateBtn = document.getElementById('generate-btn'); |
|
const colorResults = document.getElementById('color-results'); |
|
const colorCategory = document.getElementById('color-category'); |
|
const colorCount = document.getElementById('color-count'); |
|
const countDisplay = document.getElementById('count-display'); |
|
const fileUpload = document.getElementById('file-upload'); |
|
const imageUrl = document.getElementById('image-url'); |
|
const loadUrlBtn = document.getElementById('load-url-btn'); |
|
const previewImage = document.getElementById('preview-image'); |
|
const imagePreviewContainer = document.getElementById('image-preview-container'); |
|
const clearImage = document.getElementById('clear-image'); |
|
const extractColorsBtn = document.getElementById('extract-colors-btn'); |
|
const extractStatus = document.getElementById('extract-status'); |
|
const colorDetails = document.getElementById('color-details'); |
|
const copyAllBtn = document.getElementById('copy-all-btn'); |
|
const savePaletteBtn = document.getElementById('save-palette-btn'); |
|
const exportSvgBtn = document.getElementById('export-svg-btn'); |
|
const infoBtn = document.getElementById('info-btn'); |
|
const infoModal = document.getElementById('info-modal'); |
|
const closeModal = document.getElementById('close-modal'); |
|
const toast = document.getElementById('toast'); |
|
const toastMessage = document.getElementById('toast-message'); |
|
|
|
|
|
const colorThief = new ColorThief(); |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
|
countDisplay.textContent = colorCount.value; |
|
|
|
|
|
generateColors(); |
|
|
|
|
|
const darkMode = localStorage.getItem('darkMode') === 'true'; |
|
toggle.checked = darkMode; |
|
if (darkMode) { |
|
document.documentElement.classList.add('dark'); |
|
} else { |
|
document.documentElement.classList.remove('dark'); |
|
} |
|
}); |
|
|
|
|
|
toggle.addEventListener('change', toggleDarkMode); |
|
colorCount.addEventListener('input', updateCountDisplay); |
|
generateBtn.addEventListener('click', generateColors); |
|
fileUpload.addEventListener('change', handleFileUpload); |
|
loadUrlBtn.addEventListener('click', loadImageFromUrl); |
|
clearImage.addEventListener('click', clearImagePreview); |
|
extractColorsBtn.addEventListener('click', extractColorsFromImage); |
|
copyAllBtn.addEventListener('click', copyAllColors); |
|
savePaletteBtn.addEventListener('click', savePalette); |
|
exportSvgBtn.addEventListener('click', exportAsSvg); |
|
infoBtn.addEventListener('click', () => infoModal.classList.remove('hidden')); |
|
closeModal.addEventListener('click', () => infoModal.classList.add('hidden')); |
|
|
|
|
|
infoModal.addEventListener('click', (e) => { |
|
if (e.target === infoModal) { |
|
infoModal.classList.add('hidden'); |
|
} |
|
}); |
|
|
|
|
|
function toggleDarkMode() { |
|
const isDarkMode = toggle.checked; |
|
if (isDarkMode) { |
|
document.documentElement.classList.add('dark'); |
|
localStorage.setItem('darkMode', 'true'); |
|
} else { |
|
document.documentElement.classList.remove('dark'); |
|
localStorage.setItem('darkMode', 'false'); |
|
} |
|
} |
|
|
|
function updateCountDisplay() { |
|
countDisplay.textContent = colorCount.value; |
|
} |
|
|
|
function generateColors() { |
|
const count = parseInt(colorCount.value); |
|
const category = colorCategory.value; |
|
|
|
|
|
colorResults.innerHTML = ''; |
|
colorDetails.classList.add('hidden'); |
|
|
|
|
|
let colors = []; |
|
for (let i = 0; i < count; i++) { |
|
colors.push(generateColorByCategory(category)); |
|
} |
|
|
|
|
|
displayColors(colors); |
|
} |
|
|
|
function generateColorByCategory(category) { |
|
switch(category) { |
|
case 'warm': |
|
return generateWarmColor(); |
|
case 'cool': |
|
return generateCoolColor(); |
|
case 'pastel': |
|
return generatePastelColor(); |
|
case 'vibrant': |
|
return generateVibrantColor(); |
|
case 'monochrome': |
|
return generateMonochromeColor(); |
|
case 'analogous': |
|
return generateAnalogousColor(); |
|
case 'complementary': |
|
return generateComplementaryColor(); |
|
case 'triadic': |
|
return generateTriadicColor(); |
|
default: |
|
return generateRandomColor(); |
|
} |
|
} |
|
|
|
function generateRandomColor() { |
|
return { |
|
r: Math.floor(Math.random() * 256), |
|
g: Math.floor(Math.random() * 256), |
|
b: Math.floor(Math.random() * 256) |
|
}; |
|
} |
|
|
|
function generateWarmColor() { |
|
return { |
|
r: Math.floor(Math.random() * 156) + 100, |
|
g: Math.floor(Math.random() * 156), |
|
b: Math.floor(Math.random() * 106) |
|
}; |
|
} |
|
|
|
function generateCoolColor() { |
|
return { |
|
r: Math.floor(Math.random() * 106), |
|
g: Math.floor(Math.random() * 156) + 100, |
|
b: Math.floor(Math.random() * 156) + 100 |
|
}; |
|
} |
|
|
|
function generatePastelColor() { |
|
return { |
|
r: Math.floor(Math.random() * 106) + 150, |
|
g: Math.floor(Math.random() * 106) + 150, |
|
b: Math.floor(Math.random() * 106) + 150 |
|
}; |
|
} |
|
|
|
function generateVibrantColor() { |
|
|
|
const channels = ['r', 'g', 'b']; |
|
const highChannel1 = channels.splice(Math.floor(Math.random() * channels.length), 1)[0]; |
|
const highChannel2 = channels.splice(Math.floor(Math.random() * channels.length), 1)[0]; |
|
const lowChannel = channels[0]; |
|
|
|
const color = { r: 0, g: 0, b: 0 }; |
|
color[highChannel1] = Math.floor(Math.random() * 56) + 200; |
|
color[highChannel2] = Math.floor(Math.random() * 56) + 200; |
|
color[lowChannel] = Math.floor(Math.random() * 50); |
|
|
|
return color; |
|
} |
|
|
|
function generateMonochromeColor() { |
|
const base = Math.floor(Math.random() * 256); |
|
const variation = Math.floor(Math.random() * 50) - 25; |
|
return { |
|
r: Math.min(255, Math.max(0, base + variation)), |
|
g: Math.min(255, Math.max(0, base + variation)), |
|
b: Math.min(255, Math.max(0, base + variation)) |
|
}; |
|
} |
|
|
|
function generateAnalogousColor() { |
|
const baseHue = Math.floor(Math.random() * 360); |
|
const hueVariation = Math.floor(Math.random() * 30) - 15; |
|
const hue = (baseHue + hueVariation + 360) % 360; |
|
return hslToRgb(hue / 360, 0.7, 0.6); |
|
} |
|
|
|
function generateComplementaryColor() { |
|
const baseHue = Math.floor(Math.random() * 360); |
|
const hue = (baseHue + 180) % 360; |
|
return hslToRgb(hue / 360, 0.7, 0.6); |
|
} |
|
|
|
function generateTriadicColor() { |
|
const baseHue = Math.floor(Math.random() * 360); |
|
const hue = (baseHue + 120 * (Math.floor(Math.random() * 3))) % 360; |
|
return hslToRgb(hue / 360, 0.7, 0.6); |
|
} |
|
|
|
function hslToRgb(h, s, l) { |
|
let r, g, b; |
|
|
|
if (s === 0) { |
|
r = g = b = l; |
|
} else { |
|
const hue2rgb = (p, q, t) => { |
|
if (t < 0) t += 1; |
|
if (t > 1) t -= 1; |
|
if (t < 1/6) return p + (q - p) * 6 * t; |
|
if (t < 1/2) return q; |
|
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; |
|
return p; |
|
}; |
|
|
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s; |
|
const p = 2 * l - q; |
|
|
|
r = hue2rgb(p, q, h + 1/3); |
|
g = hue2rgb(p, q, h); |
|
b = hue2rgb(p, q, h - 1/3); |
|
} |
|
|
|
return { |
|
r: Math.round(r * 255), |
|
g: Math.round(g * 255), |
|
b: Math.round(b * 255) |
|
}; |
|
} |
|
|
|
function displayColors(colors) { |
|
colorResults.innerHTML = ''; |
|
|
|
colors.forEach((color, index) => { |
|
const hex = rgbToHex(color.r, color.g, color.b); |
|
const rgbText = `RGB(${color.r}, ${color.g}, ${color.b})`; |
|
|
|
const colorCard = document.createElement('div'); |
|
colorCard.className = 'color-card cursor-pointer'; |
|
colorCard.innerHTML = ` |
|
<div class="color-swatch h-24 rounded-lg shadow-md mb-2" style="background-color: ${hex};"></div> |
|
<div class="text-center"> |
|
<div class="font-mono text-sm">${hex}</div> |
|
<div class="text-xs text-gray-500 dark:text-gray-400">${rgbText}</div> |
|
</div> |
|
`; |
|
|
|
|
|
colorCard.addEventListener('click', () => showColorDetails(color)); |
|
|
|
colorResults.appendChild(colorCard); |
|
}); |
|
} |
|
|
|
function showColorDetails(color) { |
|
const hex = rgbToHex(color.r, color.g, color.b); |
|
const rgbText = `RGB(${color.r}, ${color.g}, ${color.b})`; |
|
|
|
|
|
const hsl = rgbToHsl(color.r, color.g, color.b); |
|
const hsv = rgbToHsv(color.r, color.g, color.b); |
|
const cmyk = rgbToCmyk(color.r, color.g, color.b); |
|
|
|
|
|
document.getElementById('detail-swatch').style.backgroundColor = hex; |
|
document.getElementById('detail-hex').textContent = hex; |
|
document.getElementById('detail-rgb').textContent = rgbText; |
|
document.getElementById('detail-hsl').textContent = `HSL(${Math.round(hsl.h)}, ${Math.round(hsl.s * 100)}%, ${Math.round(hsl.l * 100)}%)`; |
|
document.getElementById('detail-hsv').textContent = `HSV(${Math.round(hsv.h)}, ${Math.round(hsv.s * 100)}%, ${Math.round(hsv.v * 100)}%)`; |
|
document.getElementById('detail-cmyk').textContent = `CMYK(${Math.round(cmyk.c * 100)}%, ${Math.round(cmyk.m * 100)}%, ${Math.round(cmyk.y * 100)}%, ${Math.round(cmyk.k * 100)}%)`; |
|
|
|
|
|
document.getElementById('base-color').style.backgroundColor = hex; |
|
document.getElementById('lighten-10').style.backgroundColor = lightenColor(hex, 10); |
|
document.getElementById('lighten-20').style.backgroundColor = lightenColor(hex, 20); |
|
document.getElementById('darken-10').style.backgroundColor = darkenColor(hex, 10); |
|
document.getElementById('darken-20').style.backgroundColor = darkenColor(hex, 20); |
|
|
|
|
|
checkAccessibility(color); |
|
|
|
|
|
colorDetails.classList.remove('hidden'); |
|
} |
|
|
|
function rgbToHex(r, g, b) { |
|
return '#' + [r, g, b].map(x => { |
|
const hex = x.toString(16); |
|
return hex.length === 1 ? '0' + hex : hex; |
|
}).join(''); |
|
} |
|
|
|
function rgbToHsl(r, g, b) { |
|
r /= 255, g /= 255, b /= 255; |
|
const max = Math.max(r, g, b), min = Math.min(r, g, b); |
|
let h, s, l = (max + min) / 2; |
|
|
|
if (max === min) { |
|
h = s = 0; |
|
} else { |
|
const d = max - min; |
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min); |
|
switch(max) { |
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break; |
|
case g: h = (b - r) / d + 2; break; |
|
case b: h = (r - g) / d + 4; break; |
|
} |
|
h /= 6; |
|
} |
|
|
|
return { h: h * 360, s, l }; |
|
} |
|
|
|
function rgbToHsv(r, g, b) { |
|
r /= 255, g /= 255, b /= 255; |
|
const max = Math.max(r, g, b), min = Math.min(r, g, b); |
|
let h, s, v = max; |
|
|
|
const d = max - min; |
|
s = max === 0 ? 0 : d / max; |
|
|
|
if (max === min) { |
|
h = 0; |
|
} else { |
|
switch(max) { |
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break; |
|
case g: h = (b - r) / d + 2; break; |
|
case b: h = (r - g) / d + 4; break; |
|
} |
|
h /= 6; |
|
} |
|
|
|
return { h: h * 360, s, v }; |
|
} |
|
|
|
function rgbToCmyk(r, g, b) { |
|
|
|
let c = 1 - (r / 255); |
|
let m = 1 - (g / 255); |
|
let y = 1 - (b / 255); |
|
|
|
|
|
const minCMY = Math.min(c, m, y); |
|
|
|
|
|
c = (c - minCMY) / (1 - minCMY); |
|
m = (m - minCMY) / (1 - minCMY); |
|
y = (y - minCMY) / (1 - minCMY); |
|
const k = minCMY; |
|
|
|
|
|
if (isNaN(c)) c = 0; |
|
if (isNaN(m)) m = 0; |
|
if (isNaN(y)) y = 0; |
|
|
|
return { c, m, y, k }; |
|
} |
|
|
|
function lightenColor(hex, percent) { |
|
|
|
let r = parseInt(hex.substring(1, 3), 16); |
|
let g = parseInt(hex.substring(3, 5), 16); |
|
let b = parseInt(hex.substring(5, 7), 16); |
|
|
|
|
|
r = Math.min(255, r + Math.round(2.55 * percent)); |
|
g = Math.min(255, g + Math.round(2.55 * percent)); |
|
b = Math.min(255, b + Math.round(2.55 * percent)); |
|
|
|
|
|
return rgbToHex(r, g, b); |
|
} |
|
|
|
function darkenColor(hex, percent) { |
|
|
|
let r = parseInt(hex.substring(1, 3), 16); |
|
let g = parseInt(hex.substring(3, 5), 16); |
|
let b = parseInt(hex.substring(5, 7), 16); |
|
|
|
|
|
r = Math.max(0, r - Math.round(2.55 * percent)); |
|
g = Math.max(0, g - Math.round(2.55 * percent)); |
|
b = Math.max(0, b - Math.round(2.55 * percent)); |
|
|
|
|
|
return rgbToHex(r, g, b); |
|
} |
|
|
|
function checkAccessibility(color) { |
|
|
|
const r = color.r / 255; |
|
const g = color.g / 255; |
|
const b = color.b / 255; |
|
|
|
const rSRGB = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4); |
|
const gSRGB = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4); |
|
const bSRGB = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4); |
|
|
|
const luminance = 0.2126 * rSRGB + 0.7152 * gSRGB + 0.0722 * bSRGB; |
|
|
|
|
|
const contrastWhite = (1.0 + 0.05) / (luminance + 0.05); |
|
|
|
|
|
const contrastBlack = (luminance + 0.05) / (0.0 + 0.05); |
|
|
|
|
|
const betterContrast = contrastWhite > contrastBlack ? 'white' : 'black'; |
|
const maxContrast = Math.max(contrastWhite, contrastBlack); |
|
|
|
|
|
document.getElementById('aa-normal').textContent = maxContrast >= 4.5 ? '✔ Pass' : '✖ Fail'; |
|
document.getElementById('aa-normal').style.color = maxContrast >= 4.5 ? '#10B981' : '#EF4444'; |
|
|
|
document.getElementById('aa-large').textContent = maxContrast >= 3 ? '✔ Pass' : '✖ Fail'; |
|
document.getElementById('aa-large').style.color = maxContrast >= 3 ? '#10B981' : '#EF4444'; |
|
|
|
document.getElementById('aaa-normal').textContent = maxContrast >= 7 ? '✔ Pass' : '✖ Fail'; |
|
document.getElementById('aaa-normal').style.color = maxContrast >= 7 ? '#10B981' : '#EF4444'; |
|
} |
|
|
|
function handleFileUpload(e) { |
|
const file = e.target.files[0]; |
|
if (!file) return; |
|
|
|
const reader = new FileReader(); |
|
reader.onload = function(event) { |
|
previewImage.src = event.target.result; |
|
imagePreviewContainer.classList.remove('hidden'); |
|
extractStatus.textContent = 'Ready to extract colors'; |
|
}; |
|
reader.readAsDataURL(file); |
|
} |
|
|
|
function loadImageFromUrl() { |
|
const url = imageUrl.value.trim(); |
|
if (!url) return; |
|
|
|
extractStatus.textContent = 'Loading image...'; |
|
imagePreviewContainer.classList.remove('hidden'); |
|
|
|
|
|
const img = new Image(); |
|
img.crossOrigin = 'Anonymous'; |
|
img.onload = function() { |
|
previewImage.src = url; |
|
extractStatus.textContent = 'Ready to extract colors'; |
|
}; |
|
img.onerror = function() { |
|
extractStatus.textContent = 'Error loading image. Please check the URL.'; |
|
showToast('Error loading image', 'error'); |
|
}; |
|
img.src = url; |
|
} |
|
|
|
function clearImagePreview() { |
|
previewImage.src = ''; |
|
imagePreviewContainer.classList.add('hidden'); |
|
imageUrl.value = ''; |
|
fileUpload.value = ''; |
|
} |
|
|
|
function extractColorsFromImage() { |
|
if (!previewImage.src || previewImage.src.startsWith('data:')) { |
|
|
|
extractStatus.textContent = 'Extracting colors...'; |
|
|
|
try { |
|
const palette = colorThief.getPalette(previewImage, parseInt(colorCount.value) || 5); |
|
const colors = palette.map(color => ({ r: color[0], g: color[1], b: color[2] })); |
|
displayColors(colors); |
|
extractStatus.textContent = `Extracted ${colors.length} colors`; |
|
showToast('Colors extracted successfully!'); |
|
} catch (e) { |
|
extractStatus.textContent = 'Error extracting colors. Try another image.'; |
|
showToast('Error extracting colors', 'error'); |
|
console.error(e); |
|
} |
|
} else { |
|
|
|
extractStatus.textContent = 'Extracting colors (may take a moment)...'; |
|
|
|
const img = new Image(); |
|
img.crossOrigin = 'Anonymous'; |
|
img.onload = function() { |
|
try { |
|
const palette = colorThief.getPalette(img, parseInt(colorCount.value) || 5); |
|
const colors = palette.map(color => ({ r: color[0], g: color[1], b: color[2] })); |
|
displayColors(colors); |
|
extractStatus.textContent = `Extracted ${colors.length} colors`; |
|
showToast('Colors extracted successfully!'); |
|
} catch (e) { |
|
extractStatus.textContent = 'Error extracting colors. Try another image.'; |
|
showToast('Error extracting colors', 'error'); |
|
console.error(e); |
|
} |
|
}; |
|
img.onerror = function() { |
|
extractStatus.textContent = 'Error loading image for extraction.'; |
|
showToast('Error loading image', 'error'); |
|
}; |
|
img.src = previewImage.src; |
|
} |
|
} |
|
|
|
function copyAllColors() { |
|
const colorCards = document.querySelectorAll('.color-card'); |
|
let colorsText = ''; |
|
|
|
colorCards.forEach(card => { |
|
const hex = card.querySelector('.font-mono').textContent; |
|
const rgb = card.querySelector('.text-xs').textContent; |
|
colorsText += `${hex} ${rgb}\n`; |
|
}); |
|
|
|
navigator.clipboard.writeText(colorsText.trim()) |
|
.then(() => showToast('All colors copied to clipboard!')) |
|
.catch(err => showToast('Failed to copy colors', 'error')); |
|
} |
|
|
|
function savePalette() { |
|
const colorCards = document.querySelectorAll('.color-card'); |
|
const paletteName = prompt('Enter a name for your palette:', 'My Color Palette'); |
|
|
|
if (paletteName) { |
|
const palette = { |
|
name: paletteName, |
|
colors: [], |
|
date: new Date().toISOString() |
|
}; |
|
|
|
colorCards.forEach(card => { |
|
const hex = card.querySelector('.font-mono').textContent; |
|
palette.colors.push(hex); |
|
}); |
|
|
|
|
|
showToast(`Palette "${paletteName}" saved! (Demo)`); |
|
} |
|
} |
|
|
|
function exportAsSvg() { |
|
const colorCards = document.querySelectorAll('.color-card'); |
|
const width = 400; |
|
const height = 200; |
|
const colorWidth = width / colorCards.length; |
|
|
|
let svg = `<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">\n`; |
|
|
|
colorCards.forEach((card, index) => { |
|
const hex = card.querySelector('.font-mono').textContent; |
|
svg += ` <rect x="${index * colorWidth}" y="0" width="${colorWidth}" height="${height}" fill="${hex}" />\n`; |
|
}); |
|
|
|
svg += ` <text x="10" y="30" font-family="Arial" font-size="20" fill="white" stroke="black" stroke-width="0.5">Color Palette</text>\n`; |
|
svg += '</svg>'; |
|
|
|
|
|
const blob = new Blob([svg], { type: 'image/svg+xml' }); |
|
const url = URL.createObjectURL(blob); |
|
const a = document.createElement('a'); |
|
a.href = url; |
|
a.download = 'color-palette.svg'; |
|
document.body.appendChild(a); |
|
a.click(); |
|
document.body.removeChild(a); |
|
URL.revokeObjectURL(url); |
|
|
|
showToast('SVG exported successfully!'); |
|
} |
|
|
|
function showToast(message, type = 'success') { |
|
toastMessage.textContent = message; |
|
toast.className = `fixed bottom-4 right-4 px-4 py-2 rounded-lg shadow-lg flex items-center transform translate-y-10 opacity-0 transition-all duration-300 z-50 ${type === 'success' ? 'bg-green-500' : 'bg-red-500'} text-white`; |
|
|
|
setTimeout(() => { |
|
toast.classList.remove('translate-y-10', 'opacity-0'); |
|
toast.classList.add('translate-y-0', 'opacity-100'); |
|
}, 10); |
|
|
|
setTimeout(() => { |
|
toast.classList.remove('translate-y-0', 'opacity-100'); |
|
toast.classList.add('translate-y-10', 'opacity-0'); |
|
}, 3000); |
|
} |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=DotSlashGabut/chromagen-color-palette" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |