gradiant-picker / index.html
Decide2Design's picture
Add 3 files
7ed4968 verified
Raw History Blame Contribute Delete
45 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gradient Picker | Interactive Color Tool</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chroma-js@2.1.2/chroma.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.gradient-preview {
height: 120px;
border-radius: 0.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.color-slider {
-webkit-appearance: none;
width: 100%;
height: 8px;
border-radius: 4px;
outline: none;
}
.color-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
border: 2px solid white;
}
.color-picker-handle {
width: 24px;
height: 24px;
border-radius: 50%;
background: white;
border: 2px solid #333;
position: absolute;
top: -10px;
transform: translateX(-12px);
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.color-picker-handle::after {
content: '';
position: absolute;
width: 8px;
height: 8px;
background: #333;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.color-block {
transition: all 0.2s ease;
cursor: pointer;
}
.color-block:hover {
transform: scale(1.05);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.copy-btn {
transition: all 0.2s ease;
}
.copy-btn:hover {
transform: translateY(-2px);
}
.copy-btn:active {
transform: translateY(0);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.contrast-ratio {
font-weight: bold;
}
.contrast-ratio.aa-pass {
color: #10b981;
}
.contrast-ratio.aa-fail {
color: #ef4444;
}
.contrast-ratio.aaa-pass {
color: #10b981;
}
.contrast-ratio.aaa-fail {
color: #ef4444;
}
</style>
</head>
<body class="bg-gray-50 text-gray-800 dark:bg-gray-900 dark:text-gray-200 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-8 text-center">
<h1 class="text-3xl md:text-4xl font-bold bg-gradient-to-r from-purple-600 to-blue-500 bg-clip-text text-transparent">Gradient Picker</h1>
<p class="mt-2 text-gray-600 dark:text-gray-400">Create, explore and export beautiful color gradients</p>
</header>
<div class="max-w-4xl mx-auto bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden p-6 mb-8">
<!-- Color Inputs -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div class="color-input">
<div class="flex items-center justify-between mb-2">
<label class="font-medium">Start Color</label>
<div class="flex items-center">
<input type="text" id="color1-hex" value="#3b82f6" class="w-24 px-2 py-1 border rounded text-sm font-mono">
<button class="copy-btn ml-2 p-1 rounded-full text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100" onclick="copyToClipboard('color1-hex')">
<i class="far fa-copy"></i>
</button>
</div>
</div>
<input type="color" id="color1" value="#3b82f6" class="w-full h-10 rounded cursor-pointer">
<div class="mt-2 grid grid-cols-3 gap-2">
<div>
<label class="text-xs text-gray-500 dark:text-gray-400">RGB</label>
<input type="text" id="color1-rgb" readonly class="w-full px-2 py-1 border rounded text-sm font-mono bg-gray-50 dark:bg-gray-700">
</div>
<div>
<label class="text-xs text-gray-500 dark:text-gray-400">HSL</label>
<input type="text" id="color1-hsl" readonly class="w-full px-2 py-1 border rounded text-sm font-mono bg-gray-50 dark:bg-gray-700">
</div>
<div>
<label class="text-xs text-gray-500 dark:text-gray-400">Name</label>
<input type="text" id="color1-name" readonly class="w-full px-2 py-1 border rounded text-sm font-mono bg-gray-50 dark:bg-gray-700">
</div>
</div>
</div>
<div class="color-input">
<div class="flex items-center justify-between mb-2">
<label class="font-medium">End Color</label>
<div class="flex items-center">
<input type="text" id="color2-hex" value="#ec4899" class="w-24 px-2 py-1 border rounded text-sm font-mono">
<button class="copy-btn ml-2 p-1 rounded-full text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100" onclick="copyToClipboard('color2-hex')">
<i class="far fa-copy"></i>
</button>
</div>
</div>
<input type="color" id="color2" value="#ec4899" class="w-full h-10 rounded cursor-pointer">
<div class="mt-2 grid grid-cols-3 gap-2">
<div>
<label class="text-xs text-gray-500 dark:text-gray-400">RGB</label>
<input type="text" id="color2-rgb" readonly class="w-full px-2 py-1 border rounded text-sm font-mono bg-gray-50 dark:bg-gray-700">
</div>
<div>
<label class="text-xs text-gray-500 dark:text-gray-400">HSL</label>
<input type="text" id="color2-hsl" readonly class="w-full px-2 py-1 border rounded text-sm font-mono bg-gray-50 dark:bg-gray-700">
</div>
<div>
<label class="text-xs text-gray-500 dark:text-gray-400">Name</label>
<input type="text" id="color2-name" readonly class="w-full px-2 py-1 border rounded text-sm font-mono bg-gray-50 dark:bg-gray-700">
</div>
</div>
</div>
</div>
<!-- Gradient Controls -->
<div class="mb-6">
<div class="flex items-center justify-between mb-2">
<label class="font-medium">Gradient Steps</label>
<span id="steps-value" class="font-mono">5</span>
</div>
<input type="range" id="steps" min="2" max="20" value="5" class="color-slider">
</div>
<!-- Gradient Preview -->
<div class="mb-6 relative">
<div id="gradient-preview" class="gradient-preview" style="background: linear-gradient(to right, #3b82f6, #ec4899);"></div>
<div id="color-picker-handle" class="color-picker-handle" style="left: 50%;"></div>
</div>
<!-- Picked Color Info -->
<div id="picked-color-info" class="bg-gray-100 dark:bg-gray-700 rounded-lg p-4 mb-6 hidden">
<div class="flex items-center justify-between">
<div class="flex items-center">
<div id="picked-color-swatch" class="w-10 h-10 rounded-full border-2 border-white shadow mr-3"></div>
<div>
<div class="font-medium">Selected Color</div>
<div id="picked-color-hex" class="text-sm font-mono">#9d7dce</div>
</div>
</div>
<button id="copy-picked-color" class="copy-btn px-3 py-1 bg-gray-200 dark:bg-gray-600 rounded-full text-sm flex items-center">
<i class="far fa-copy mr-1"></i> Copy
</button>
</div>
<div class="mt-3 grid grid-cols-3 gap-2">
<div>
<label class="text-xs text-gray-500 dark:text-gray-400">RGB</label>
<input type="text" id="picked-color-rgb" readonly class="w-full px-2 py-1 border rounded text-sm font-mono bg-gray-50 dark:bg-gray-700">
</div>
<div>
<label class="text-xs text-gray-500 dark:text-gray-400">HSL</label>
<input type="text" id="picked-color-hsl" readonly class="w-full px-2 py-1 border rounded text-sm font-mono bg-gray-50 dark:bg-gray-700">
</div>
<div>
<label class="text-xs text-gray-500 dark:text-gray-400">Name</label>
<input type="text" id="picked-color-name" readonly class="w-full px-2 py-1 border rounded text-sm font-mono bg-gray-50 dark:bg-gray-700">
</div>
</div>
</div>
<!-- Color Steps -->
<div class="mb-6">
<h3 class="font-medium mb-3">Color Steps</h3>
<div id="color-steps" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
<!-- Color blocks will be generated here -->
</div>
</div>
<!-- Tabs -->
<div class="mb-6">
<div class="flex border-b border-gray-200 dark:border-gray-700">
<button class="tab-btn px-4 py-2 font-medium text-blue-600 dark:text-blue-400 border-b-2 border-blue-600 dark:border-blue-400" data-tab="export">Export</button>
<button class="tab-btn px-4 py-2 font-medium text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100" data-tab="harmonies">Color Harmonies</button>
<button class="tab-btn px-4 py-2 font-medium text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100" data-tab="contrast">Contrast Checker</button>
<button class="tab-btn px-4 py-2 font-medium text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100" data-tab="preview">UI Preview</button>
</div>
<div id="export" class="tab-content active mt-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-2">CSS Gradient</label>
<div class="relative">
<textarea id="css-gradient" readonly class="w-full h-20 px-3 py-2 border rounded font-mono text-sm bg-gray-50 dark:bg-gray-700"></textarea>
<button onclick="copyToClipboard('css-gradient')" class="absolute top-2 right-2 copy-btn px-2 py-1 bg-gray-200 dark:bg-gray-600 rounded text-xs">
<i class="far fa-copy mr-1"></i> Copy
</button>
</div>
</div>
<div>
<label class="block text-sm font-medium mb-2">SVG Gradient</label>
<div class="relative">
<textarea id="svg-gradient" readonly class="w-full h-20 px-3 py-2 border rounded font-mono text-sm bg-gray-50 dark:bg-gray-700"></textarea>
<button onclick="copyToClipboard('svg-gradient')" class="absolute top-2 right-2 copy-btn px-2 py-1 bg-gray-200 dark:bg-gray-600 rounded text-xs">
<i class="far fa-copy mr-1"></i> Copy
</button>
</div>
</div>
</div>
<div class="mt-4">
<button id="download-png" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded flex items-center">
<i class="fas fa-download mr-2"></i> Download PNG
</button>
</div>
</div>
<div id="harmonies" class="tab-content mt-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h4 class="font-medium mb-3">Complementary</h4>
<div id="complementary-colors" class="grid grid-cols-5 gap-2">
<!-- Complementary colors will be generated here -->
</div>
</div>
<div>
<h4 class="font-medium mb-3">Analogous</h4>
<div id="analogous-colors" class="grid grid-cols-3 gap-2">
<!-- Analogous colors will be generated here -->
</div>
</div>
<div>
<h4 class="font-medium mb-3">Triadic</h4>
<div id="triadic-colors" class="grid grid-cols-3 gap-2">
<!-- Triadic colors will be generated here -->
</div>
</div>
<div>
<h4 class="font-medium mb-3">Tetradic</h4>
<div id="tetradic-colors" class="grid grid-cols-4 gap-2">
<!-- Tetradic colors will be generated here -->
</div>
</div>
</div>
</div>
<div id="contrast" class="tab-content mt-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h4 class="font-medium mb-3">Contrast Ratio</h4>
<div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg">
<div class="flex items-center justify-between mb-2">
<span>WCAG AA (4.5:1)</span>
<span id="contrast-aa" class="contrast-ratio aa-pass">4.8:1</span>
</div>
<div class="flex items-center justify-between mb-2">
<span>WCAG AAA (7:1)</span>
<span id="contrast-aaa" class="contrast-ratio aaa-fail">4.8:1</span>
</div>
<div class="mt-4">
<label class="block text-sm font-medium mb-2">Test Background</label>
<input type="color" id="contrast-bg" value="#ffffff" class="w-full h-10 rounded cursor-pointer">
</div>
</div>
</div>
<div>
<h4 class="font-medium mb-3">Preview</h4>
<div id="contrast-preview" class="p-4 rounded-lg" style="background-color: #ffffff;">
<p class="text-lg" style="color: #3b82f6;">This is sample text to demonstrate contrast</p>
<p class="text-sm mt-2" style="color: #3b82f6;">Smaller text requires higher contrast</p>
</div>
</div>
</div>
</div>
<div id="preview" class="tab-content mt-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg">
<div class="flex items-center justify-between mb-3">
<h4 class="font-medium">Card</h4>
<div class="flex">
<button class="preview-mode-btn px-2 py-1 text-xs bg-gray-200 dark:bg-gray-600 rounded-l" data-mode="light">Light</button>
<button class="preview-mode-btn px-2 py-1 text-xs bg-gray-300 dark:bg-gray-800 rounded-r" data-mode="dark">Dark</button>
</div>
</div>
<div class="preview-card p-4 rounded-lg shadow" style="background: linear-gradient(to right, #3b82f6, #ec4899);">
<h5 class="font-bold text-white">Card Title</h5>
<p class="text-white text-opacity-90 text-sm mt-1">This is a preview card with your gradient</p>
</div>
</div>
</div>
<div>
<div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg">
<h4 class="font-medium mb-3">Buttons</h4>
<div class="grid grid-cols-2 gap-3">
<button class="px-4 py-2 rounded text-white font-medium" style="background-color: #3b82f6;">Primary</button>
<button class="px-4 py-2 rounded text-white font-medium" style="background-color: #ec4899;">Secondary</button>
<button class="px-4 py-2 rounded font-medium border" style="border-color: #3b82f6; color: #3b82f6;">Outline</button>
<button class="px-4 py-2 rounded font-medium border" style="border-color: #ec4899; color: #ec4899;">Outline</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="text-center text-gray-600 dark:text-gray-400 text-sm mt-8">
<p>Gradient Picker &copy; 2023 | Made with <i class="fas fa-heart text-red-500"></i> for designers & developers</p>
</footer>
</div>
<script>
// DOM Elements
const color1Input = document.getElementById('color1');
const color1HexInput = document.getElementById('color1-hex');
const color1RgbInput = document.getElementById('color1-rgb');
const color1HslInput = document.getElementById('color1-hsl');
const color1NameInput = document.getElementById('color1-name');
const color2Input = document.getElementById('color2');
const color2HexInput = document.getElementById('color2-hex');
const color2RgbInput = document.getElementById('color2-rgb');
const color2HslInput = document.getElementById('color2-hsl');
const color2NameInput = document.getElementById('color2-name');
const stepsInput = document.getElementById('steps');
const stepsValue = document.getElementById('steps-value');
const gradientPreview = document.getElementById('gradient-preview');
const colorPickerHandle = document.getElementById('color-picker-handle');
const pickedColorInfo = document.getElementById('picked-color-info');
const pickedColorSwatch = document.getElementById('picked-color-swatch');
const pickedColorHex = document.getElementById('picked-color-hex');
const pickedColorRgb = document.getElementById('picked-color-rgb');
const pickedColorHsl = document.getElementById('picked-color-hsl');
const pickedColorName = document.getElementById('picked-color-name');
const copyPickedColorBtn = document.getElementById('copy-picked-color');
const colorStepsContainer = document.getElementById('color-steps');
const cssGradientTextarea = document.getElementById('css-gradient');
const svgGradientTextarea = document.getElementById('svg-gradient');
const downloadPngBtn = document.getElementById('download-png');
const complementaryColorsContainer = document.getElementById('complementary-colors');
const analogousColorsContainer = document.getElementById('analogous-colors');
const triadicColorsContainer = document.getElementById('triadic-colors');
const tetradicColorsContainer = document.getElementById('tetradic-colors');
const contrastRatioAA = document.getElementById('contrast-aa');
const contrastRatioAAA = document.getElementById('contrast-aaa');
const contrastBgInput = document.getElementById('contrast-bg');
const contrastPreview = document.getElementById('contrast-preview');
// Tab functionality
const tabButtons = document.querySelectorAll('.tab-btn');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
// Remove active class from all buttons and content
tabButtons.forEach(btn => {
btn.classList.remove('text-blue-600', 'dark:text-blue-400', 'border-b-2', 'border-blue-600', 'dark:border-blue-400');
btn.classList.add('text-gray-600', 'dark:text-gray-400');
});
// Add active class to clicked button
button.classList.add('text-blue-600', 'dark:text-blue-400', 'border-b-2', 'border-blue-600', 'dark:border-blue-400');
button.classList.remove('text-gray-600', 'dark:text-gray-400');
// Hide all tab content
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// Show selected tab content
const tabId = button.getAttribute('data-tab');
document.getElementById(tabId).classList.add('active');
});
});
// Preview mode (light/dark)
const previewModeButtons = document.querySelectorAll('.preview-mode-btn');
previewModeButtons.forEach(button => {
button.addEventListener('click', () => {
previewModeButtons.forEach(btn => {
btn.classList.remove('bg-gray-300', 'dark:bg-gray-800');
btn.classList.add('bg-gray-200', 'dark:bg-gray-600');
});
button.classList.add('bg-gray-300', 'dark:bg-gray-800');
button.classList.remove('bg-gray-200', 'dark:bg-gray-600');
const mode = button.getAttribute('data-mode');
const previewCard = document.querySelector('.preview-card');
if (mode === 'dark') {
previewCard.classList.add('dark-mode-preview');
previewCard.querySelector('h5').classList.add('dark:text-white');
previewCard.querySelector('p').classList.add('dark:text-white');
} else {
previewCard.classList.remove('dark-mode-preview');
previewCard.querySelector('h5').classList.remove('dark:text-white');
previewCard.querySelector('p').classList.remove('dark:text-white');
}
});
});
// Initialize
function init() {
updateColorInfo(color1Input, color1HexInput, color1RgbInput, color1HslInput, color1NameInput);
updateColorInfo(color2Input, color2HexInput, color2RgbInput, color2HslInput, color2NameInput);
updateGradient();
updateColorSteps();
updateColorHarmonies();
updateContrastRatio();
// Set initial tab
document.querySelector('.tab-btn[data-tab="export"]').click();
}
// Update color information
function updateColorInfo(colorInput, hexInput, rgbInput, hslInput, nameInput) {
const color = chroma(colorInput.value);
// Update hex value
hexInput.value = color.hex();
// Update RGB value
const rgb = color.rgb();
rgbInput.value = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
// Update HSL value
const hsl = color.hsl();
hslInput.value = `hsl(${Math.round(hsl[0])}, ${Math.round(hsl[1] * 100)}%, ${Math.round(hsl[2] * 100)}%)`;
// Update color name (simplified)
nameInput.value = getColorName(color.hex());
}
// Simple color name lookup
function getColorName(hex) {
const colorMap = {
'#3b82f6': 'Blue 500',
'#ec4899': 'Pink 500',
'#ffffff': 'White',
'#000000': 'Black',
'#f59e0b': 'Amber 500',
'#10b981': 'Emerald 500',
'#ef4444': 'Red 500',
'#8b5cf6': 'Violet 500',
'#f97316': 'Orange 500',
'#14b8a6': 'Teal 500'
};
return colorMap[hex.toLowerCase()] || 'Custom Color';
}
// Update gradient preview
function updateGradient() {
const color1 = color1HexInput.value;
const color2 = color2HexInput.value;
gradientPreview.style.background = `linear-gradient(to right, ${color1}, ${color2})`;
// Update CSS gradient code
cssGradientTextarea.value = `background: linear-gradient(to right, ${color1}, ${color2});`;
// Update SVG gradient code
svgGradientTextarea.value = `<svg width="100%" height="100%">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="${color1}" />
<stop offset="100%" stop-color="${color2}" />
</linearGradient>
</defs>
<rect width="100%" height="100%" fill="url(#gradient)" />
</svg>`;
// Update UI preview elements
document.querySelectorAll('.preview-card').forEach(card => {
card.style.background = `linear-gradient(to right, ${color1}, ${color2})`;
});
document.querySelectorAll('.preview-card h5, .preview-card p').forEach(text => {
// Choose text color with better contrast
const contrast1 = chroma.contrast(color1, '#ffffff');
const contrast2 = chroma.contrast(color2, '#ffffff');
text.style.color = contrast1 > contrast2 ? '#ffffff' : '#000000';
});
// Update buttons in preview
const buttons = document.querySelectorAll('#preview button');
if (buttons.length >= 4) {
buttons[0].style.backgroundColor = color1;
buttons[1].style.backgroundColor = color2;
buttons[2].style.borderColor = color1;
buttons[2].style.color = color1;
buttons[3].style.borderColor = color2;
buttons[3].style.color = color2;
}
}
// Update color steps
function updateColorSteps() {
const steps = parseInt(stepsInput.value);
stepsValue.textContent = steps;
const color1 = color1HexInput.value;
const color2 = color2HexInput.value;
colorStepsContainer.innerHTML = '';
// Generate color steps
for (let i = 0; i < steps; i++) {
const t = i / (steps - 1);
const color = chroma.mix(color1, color2, t);
const hex = color.hex();
const rgb = color.rgb();
const rgbStr = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
const colorBlock = document.createElement('div');
colorBlock.className = 'color-block h-16 rounded-lg flex flex-col items-center justify-center text-white text-xs font-medium';
colorBlock.style.backgroundColor = hex;
colorBlock.innerHTML = `
<span class="font-mono">${hex}</span>
<button class="copy-btn mt-1 px-2 py-1 bg-black bg-opacity-20 rounded-full text-xs" data-color="${hex}">
<i class="far fa-copy mr-1"></i> Copy
</button>
`;
// Add click event to show color info
colorBlock.addEventListener('click', () => {
showPickedColorInfo(hex);
});
// Add click event to copy button
const copyBtn = colorBlock.querySelector('button');
copyBtn.addEventListener('click', (e) => {
e.stopPropagation();
copyToClipboardFromAttribute(copyBtn, 'data-color');
// Show feedback
const originalText = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="fas fa-check mr-1"></i> Copied!';
setTimeout(() => {
copyBtn.innerHTML = originalText;
}, 1500);
});
colorStepsContainer.appendChild(colorBlock);
}
}
// Show picked color info
function showPickedColorInfo(hex) {
const color = chroma(hex);
pickedColorSwatch.style.backgroundColor = hex;
pickedColorHex.textContent = hex;
const rgb = color.rgb();
pickedColorRgb.value = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
const hsl = color.hsl();
pickedColorHsl.value = `hsl(${Math.round(hsl[0])}, ${Math.round(hsl[1] * 100)}%, ${Math.round(hsl[2] * 100)}%)`;
pickedColorName.value = getColorName(hex);
pickedColorInfo.classList.remove('hidden');
// Position the handle on the gradient
const color1 = color1HexInput.value;
const color2 = color2HexInput.value;
if (hex.toLowerCase() === color1.toLowerCase()) {
colorPickerHandle.style.left = '0%';
} else if (hex.toLowerCase() === color2.toLowerCase()) {
colorPickerHandle.style.left = '100%';
} else {
// Find position in gradient
const steps = parseInt(stepsInput.value);
let closestPos = 50;
for (let i = 0; i < steps; i++) {
const t = i / (steps - 1);
const stepColor = chroma.mix(color1, color2, t);
if (stepColor.hex().toLowerCase() === hex.toLowerCase()) {
closestPos = t * 100;
break;
}
}
colorPickerHandle.style.left = `${closestPos}%`;
}
}
// Update color harmonies
function updateColorHarmonies() {
const color = chroma(color1HexInput.value);
// Complementary colors
complementaryColorsContainer.innerHTML = '';
const complementary = color.set('hsl.h', (color.get('hsl.h') + 180) % 360);
addColorToHarmony(complementaryColorsContainer, color.hex());
addColorToHarmony(complementaryColorsContainer, complementary.hex());
// Analogous colors
analogousColorsContainer.innerHTML = '';
const analogous1 = color.set('hsl.h', (color.get('hsl.h') + 30) % 360);
const analogous2 = color.set('hsl.h', (color.get('hsl.h') - 30 + 360) % 360);
addColorToHarmony(analogousColorsContainer, analogous2.hex());
addColorToHarmony(analogousColorsContainer, color.hex());
addColorToHarmony(analogousColorsContainer, analogous1.hex());
// Triadic colors
triadicColorsContainer.innerHTML = '';
const triadic1 = color.set('hsl.h', (color.get('hsl.h') + 120) % 360);
const triadic2 = color.set('hsl.h', (color.get('hsl.h') + 240) % 360);
addColorToHarmony(triadicColorsContainer, color.hex());
addColorToHarmony(triadicColorsContainer, triadic1.hex());
addColorToHarmony(triadicColorsContainer, triadic2.hex());
// Tetradic colors
tetradicColorsContainer.innerHTML = '';
const tetradic1 = color.set('hsl.h', (color.get('hsl.h') + 90) % 360);
const tetradic2 = color.set('hsl.h', (color.get('hsl.h') + 180) % 360);
const tetradic3 = color.set('hsl.h', (color.get('hsl.h') + 270) % 360);
addColorToHarmony(tetradicColorsContainer, color.hex());
addColorToHarmony(tetradicColorsContainer, tetradic1.hex());
addColorToHarmony(tetradicColorsContainer, tetradic2.hex());
addColorToHarmony(tetradicColorsContainer, tetradic3.hex());
}
// Add color to harmony container
function addColorToHarmony(container, hex) {
const colorBlock = document.createElement('div');
colorBlock.className = 'color-block h-16 rounded-lg flex flex-col items-center justify-center text-white text-xs font-medium';
colorBlock.style.backgroundColor = hex;
colorBlock.innerHTML = `
<span class="font-mono">${hex}</span>
<button class="copy-btn mt-1 px-2 py-1 bg-black bg-opacity-20 rounded-full text-xs" data-color="${hex}">
<i class="far fa-copy mr-1"></i> Copy
</button>
`;
// Add click event to show color info
colorBlock.addEventListener('click', () => {
showPickedColorInfo(hex);
});
// Add click event to copy button
const copyBtn = colorBlock.querySelector('button');
copyBtn.addEventListener('click', (e) => {
e.stopPropagation();
copyToClipboardFromAttribute(copyBtn, 'data-color');
// Show feedback
const originalText = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="fas fa-check mr-1"></i> Copied!';
setTimeout(() => {
copyBtn.innerHTML = originalText;
}, 1500);
});
container.appendChild(colorBlock);
}
// Update contrast ratio
function updateContrastRatio() {
const color = color1HexInput.value;
const bgColor = contrastBgInput.value;
const ratio = chroma.contrast(color, bgColor);
contrastRatioAA.textContent = ratio.toFixed(1) + ':1';
contrastRatioAAA.textContent = ratio.toFixed(1) + ':1';
// Update classes based on WCAG guidelines
contrastRatioAA.classList.remove('aa-pass', 'aa-fail');
contrastRatioAAA.classList.remove('aaa-pass', 'aaa-fail');
if (ratio >= 4.5) {
contrastRatioAA.classList.add('aa-pass');
} else {
contrastRatioAA.classList.add('aa-fail');
}
if (ratio >= 7) {
contrastRatioAAA.classList.add('aaa-pass');
} else {
contrastRatioAAA.classList.add('aaa-fail');
}
// Update preview
contrastPreview.style.backgroundColor = bgColor;
const previewText = contrastPreview.querySelectorAll('p');
previewText.forEach(text => {
text.style.color = color;
});
}
// Copy to clipboard
function copyToClipboard(elementId) {
const element = document.getElementById(elementId);
element.select();
document.execCommand('copy');
// Show feedback
const originalText = element.value;
if (elementId === 'css-gradient' || elementId === 'svg-gradient') {
const button = element.nextElementSibling;
const originalButtonText = button.innerHTML;
button.innerHTML = '<i class="fas fa-check mr-1"></i> Copied!';
setTimeout(() => {
button.innerHTML = originalButtonText;
}, 1500);
}
}
function copyToClipboardFromAttribute(element, attribute) {
const text = element.getAttribute(attribute);
navigator.clipboard.writeText(text);
}
// Download PNG
function downloadPng() {
alert('PNG download functionality would be implemented in a real application using a canvas library like html2canvas.');
// In a real implementation, you would use html2canvas or similar to capture the gradient
// and then create a download link for the PNG image.
}
// Event listeners
color1Input.addEventListener('input', () => {
color1HexInput.value = color1Input.value;
updateColorInfo(color1Input, color1HexInput, color1RgbInput, color1HslInput, color1NameInput);
updateGradient();
updateColorSteps();
updateColorHarmonies();
updateContrastRatio();
});
color1HexInput.addEventListener('change', () => {
if (chroma.valid(color1HexInput.value)) {
color1Input.value = chroma(color1HexInput.value).hex();
updateColorInfo(color1Input, color1HexInput, color1RgbInput, color1HslInput, color1NameInput);
updateGradient();
updateColorSteps();
updateColorHarmonies();
updateContrastRatio();
} else {
alert('Invalid HEX color code');
color1HexInput.value = color1Input.value;
}
});
color2Input.addEventListener('input', () => {
color2HexInput.value = color2Input.value;
updateColorInfo(color2Input, color2HexInput, color2RgbInput, color2HslInput, color2NameInput);
updateGradient();
updateColorSteps();
updateContrastRatio();
});
color2HexInput.addEventListener('change', () => {
if (chroma.valid(color2HexInput.value)) {
color2Input.value = chroma(color2HexInput.value).hex();
updateColorInfo(color2Input, color2HexInput, color2RgbInput, color2HslInput, color2NameInput);
updateGradient();
updateColorSteps();
updateContrastRatio();
} else {
alert('Invalid HEX color code');
color2HexInput.value = color2Input.value;
}
});
stepsInput.addEventListener('input', () => {
updateColorSteps();
});
// Gradient picker handle interaction
let isDragging = false;
colorPickerHandle.addEventListener('mousedown', () => {
isDragging = true;
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
const gradientRect = gradientPreview.getBoundingClientRect();
let x = e.clientX - gradientRect.left;
x = Math.max(0, Math.min(x, gradientRect.width));
const percentage = (x / gradientRect.width) * 100;
colorPickerHandle.style.left = `${percentage}%`;
// Calculate color at this position
const color1 = color1HexInput.value;
const color2 = color2HexInput.value;
const color = chroma.mix(color1, color2, percentage / 100);
// Update picked color info
pickedColorSwatch.style.backgroundColor = color.hex();
pickedColorHex.textContent = color.hex();
const rgb = color.rgb();
pickedColorRgb.value = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
const hsl = color.hsl();
pickedColorHsl.value = `hsl(${Math.round(hsl[0])}, ${Math.round(hsl[1] * 100)}%, ${Math.round(hsl[2] * 100)}%)`;
pickedColorName.value = getColorName(color.hex());
pickedColorInfo.classList.remove('hidden');
}
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
gradientPreview.addEventListener('click', (e) => {
const gradientRect = gradientPreview.getBoundingClientRect();
let x = e.clientX - gradientRect.left;
x = Math.max(0, Math.min(x, gradientRect.width));
const percentage = (x / gradientRect.width) * 100;
colorPickerHandle.style.left = `${percentage}%`;
// Calculate color at this position
const color1 = color1HexInput.value;
const color2 = color2HexInput.value;
const color = chroma.mix(color1, color2, percentage / 100);
// Update picked color info
pickedColorSwatch.style.backgroundColor = color.hex();
pickedColorHex.textContent = color.hex();
const rgb = color.rgb();
pickedColorRgb.value = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
const hsl = color.hsl();
pickedColorHsl.value = `hsl(${Math.round(hsl[0])}, ${Math.round(hsl[1] * 100)}%, ${Math.round(hsl[2] * 100)}%)`;
pickedColorName.value = getColorName(color.hex());
pickedColorInfo.classList.remove('hidden');
});
copyPickedColorBtn.addEventListener('click', () => {
navigator.clipboard.writeText(pickedColorHex.textContent);
// Show feedback
const originalText = copyPickedColorBtn.innerHTML;
copyPickedColorBtn.innerHTML = '<i class="fas fa-check mr-1"></i> Copied!';
setTimeout(() => {
copyPickedColorBtn.innerHTML = originalText;
}, 1500);
});
contrastBgInput.addEventListener('input', () => {
updateContrastRatio();
});
downloadPngBtn.addEventListener('click', downloadPng);
// Initialize the app
init();
</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=Decide2Design/gradiant-picker" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>