DotSlashGabut's picture
the ui still not change to dark mode or white mode. fix that - Initial Deployment
d823205 verified
<!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 -->
<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 Content -->
<main class="flex-grow container mx-auto px-4 py-8">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Controls Section -->
<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">
<!-- Color Category -->
<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>
<!-- Color Count -->
<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>
<!-- Generate Button -->
<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>
<!-- Divider -->
<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>
<!-- Image Extraction -->
<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>
<!-- Image Preview -->
<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>
<!-- Results Section -->
<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">
<!-- Colors will be generated here -->
<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>
<!-- Color Details -->
<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 -->
<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 &copy; 2023 | Advanced Color Palette Generator</p>
</div>
</footer>
</div>
<!-- Info Modal -->
<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>
<!-- Toast Notification -->
<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>
// DOM Elements
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');
// Color Thief instance
const colorThief = new ColorThief();
// Initialize
document.addEventListener('DOMContentLoaded', () => {
// Set initial color count display
countDisplay.textContent = colorCount.value;
// Generate initial colors
generateColors();
// Check for saved dark mode preference
const darkMode = localStorage.getItem('darkMode') === 'true';
toggle.checked = darkMode;
if (darkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
});
// Event Listeners
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'));
// Close modal when clicking outside
infoModal.addEventListener('click', (e) => {
if (e.target === infoModal) {
infoModal.classList.add('hidden');
}
});
// Functions
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;
// Clear previous results
colorResults.innerHTML = '';
colorDetails.classList.add('hidden');
// Generate new colors based on category
let colors = [];
for (let i = 0; i < count; i++) {
colors.push(generateColorByCategory(category));
}
// Display colors
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, // 100-255
g: Math.floor(Math.random() * 156), // 0-155
b: Math.floor(Math.random() * 106) // 0-105
};
}
function generateCoolColor() {
return {
r: Math.floor(Math.random() * 106), // 0-105
g: Math.floor(Math.random() * 156) + 100, // 100-255
b: Math.floor(Math.random() * 156) + 100 // 100-255
};
}
function generatePastelColor() {
return {
r: Math.floor(Math.random() * 106) + 150, // 150-255
g: Math.floor(Math.random() * 106) + 150, // 150-255
b: Math.floor(Math.random() * 106) + 150 // 150-255
};
}
function generateVibrantColor() {
// At least two channels at 200+ and one channel at <50
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; // 200-255
color[highChannel2] = Math.floor(Math.random() * 56) + 200; // 200-255
color[lowChannel] = Math.floor(Math.random() * 50); // 0-49
return color;
}
function generateMonochromeColor() {
const base = Math.floor(Math.random() * 256);
const variation = Math.floor(Math.random() * 50) - 25; // -25 to +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; // -15 to +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; // achromatic
} 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>
`;
// Add click event to show color details
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})`;
// Convert to HSL, HSV, CMYK
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);
// Update details
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)}%)`;
// Update variations
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);
// Check accessibility
checkAccessibility(color);
// Show details panel
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; // achromatic
} 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; // achromatic
} 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) {
// Convert RGB to CMY
let c = 1 - (r / 255);
let m = 1 - (g / 255);
let y = 1 - (b / 255);
// Find the minimum of CMY
const minCMY = Math.min(c, m, y);
// Convert CMY to CMYK
c = (c - minCMY) / (1 - minCMY);
m = (m - minCMY) / (1 - minCMY);
y = (y - minCMY) / (1 - minCMY);
const k = minCMY;
// Handle the case when all components are 0
if (isNaN(c)) c = 0;
if (isNaN(m)) m = 0;
if (isNaN(y)) y = 0;
return { c, m, y, k };
}
function lightenColor(hex, percent) {
// Convert hex to RGB
let r = parseInt(hex.substring(1, 3), 16);
let g = parseInt(hex.substring(3, 5), 16);
let b = parseInt(hex.substring(5, 7), 16);
// Lighten each component
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));
// Convert back to hex
return rgbToHex(r, g, b);
}
function darkenColor(hex, percent) {
// Convert hex to RGB
let r = parseInt(hex.substring(1, 3), 16);
let g = parseInt(hex.substring(3, 5), 16);
let b = parseInt(hex.substring(5, 7), 16);
// Darken each component
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));
// Convert back to hex
return rgbToHex(r, g, b);
}
function checkAccessibility(color) {
// Calculate relative luminance (WCAG formula)
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;
// Check contrast against white (1.0 luminance)
const contrastWhite = (1.0 + 0.05) / (luminance + 0.05);
// Check contrast against black (0.0 luminance)
const contrastBlack = (luminance + 0.05) / (0.0 + 0.05);
// Determine which has better contrast
const betterContrast = contrastWhite > contrastBlack ? 'white' : 'black';
const maxContrast = Math.max(contrastWhite, contrastBlack);
// Set accessibility ratings
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');
// Create a new image to check if the URL is valid
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:')) {
// Local image
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 {
// Remote image - need to handle CORS
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);
});
// In a real app, you would save to localStorage or a database
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>';
// Create download link
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>