Nervelle Studios
Ultra Detailed Color Picker (Single File) — Restored

Ultra Detailed Color Picker

Drag, type, convert, copy — with palettes, contrast & gradients
Tip: you can type into HEX/RGB/HSL boxes — everything stays in sync.

Preview & Contrast

Aa
Lorem ipsum dolor sit amet — 16px body text.

Palettes (Tints/Shades) & Harmonies

Gradient Builder & History

Pro tips: Drag on the square to set Saturation (x) and Value/Brightness (y). Use Eyedrop to sample any on‑screen color (supported in most Chromium browsers). Background picker blends your current color over it for an accurate WCAG contrast (even when transparent). Click any swatch to copy its HEX.

${hex}`; node.appendChild(el); }); } function renderPalettes({r,g,b}){ const baseHsl = rgbToHsl(r,g,b); const steps = [5,10,15,25,35,50,65,75,85,92]; const ts = steps.map(L=>{ const {r:rr,g:gg,b:bb} = hslToRgb(baseHsl.h, baseHsl.s, L/100); return toHex({r:rr,g:gg,b:bb,a:1}, false); }); renderSwatches(tintsShades, ts); const anHues = [-60,-30,0,30,60].map(d=> ((baseHsl.h + d + 360) % 360)); const analogousArr = anHues.map(h=>{ const {r:rr,g:gg,b:bb} = hslToRgb(h, baseHsl.s, baseHsl.l); return toHex({r:rr,g:gg,b:bb,a:1}, false); }); renderSwatches(analogous, analogousArr); const comp = (baseHsl.h + 180) % 360; const split1 = (baseHsl.h + 150) % 360; const split2 = (baseHsl.h + 210) % 360; const comps = [baseHsl.h, split1, comp, split2].map(h=>{ const {r:rr,g:gg,b:bb} = hslToRgb(h, baseHsl.s, baseHsl.l); return toHex({r:rr,g:gg,b:bb,a:1}, false); }); renderSwatches(complements, comps); const tri = [(baseHsl.h+0)%360, (baseHsl.h+120)%360, (baseHsl.h+240)%360]; const tet = [(baseHsl.h+0)%360, (baseHsl.h+90)%360, (baseHsl.h+180)%360, (baseHsl.h+270)%360]; const triT = [...tri, ...tet.filter(h=>!tri.includes(h))].map(h=>{ const {r:rr,g:gg,b:bb} = hslToRgb(h, baseHsl.s, baseHsl.l); return toHex({r:rr,g:gg,b:bb,a:1}, false); }); renderSwatches(triTetra, triT); } // ========================= Gradient Builder ========================= function updateGradient(){ const start = anyToRgba(gradStart.value) || anyToRgba(hexInput.value); const end = anyToRgba(gradEnd.value) || {r:0,g:0,b:0,a:1}; const a = clamp(Number(gradAngle.value)||0, 0, 360); const css = `linear-gradient(${a}deg, ${fmtRGB(start.r,start.g,start.b,start.a)} 0%, ${fmtRGB(end.r,end.g,end.b,end.a)} 100%)`; gradPreview.firstElementChild.style.background = css; gradCss.value = `background: ${css};`; } gradStart.addEventListener('change', updateGradient); gradEnd.addEventListener('change', updateGradient); gradAngle.addEventListener('input', updateGradient); copyGradient.addEventListener('click', ()=> copyText(gradCss.value)); // ========================= History (LocalStorage) ========================= const HISTORY_KEY = 'ultraColorPickerHistory'; function getHistory(){ try{ return JSON.parse(localStorage.getItem(HISTORY_KEY)||'[]'); }catch{ return []; } } function setHistory(arr){ localStorage.setItem(HISTORY_KEY, JSON.stringify(arr.slice(0, 24))); } function renderHistory(){ const arr = getHistory(); historyWrap.innerHTML = ''; arr.forEach(hex=>{ const el = document.createElement('div'); el.className = 'swatch'; el.innerHTML = `
${hex}`; el.addEventListener('click', ()=>{ hexInput.value = hex; setFromHex(); }); historyWrap.appendChild(el); }); } saveColor.addEventListener('click', ()=>{ const arr = getHistory(); if (!arr.includes(hexInput.value)) arr.unshift(hexInput.value); setHistory(arr); renderHistory(); toast('Saved to history'); }); clearHistoryBtn.addEventListener('click', ()=>{ setHistory([]); renderHistory(); }); // ========================= Export CSS Variables ========================= exportCss.addEventListener('click', ()=>{ const vars = [ `--color-current: ${rgbInput.value};`, `--color-current-hex: ${hexInput.value};` ].join('\n'); const css = `:root{\n${vars}\n}`; downloadText('color-vars.css', css); }); function downloadText(filename, content){ const blob = new Blob([content], {type:'text/css'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); setTimeout(()=> URL.revokeObjectURL(url), 1000); } // ========================= Randomize & Helpers ========================= randomize.addEventListener('click', ()=>{ state.h = Math.floor(Math.random()*360); state.s = Math.random(); state.v = lerp(0.5, 1, Math.random()); state.a = lerp(0.6, 1, Math.random()); updateAll('rand'); }); applyRecommend.addEventListener('click', ()=>{ const c = anyToRgba(recommendChip.textContent); if (c) setFromRgbObject({...c, a:1}); }); bgInput.addEventListener('change', ()=> updateAll('bg')); // ========================= Micro Toast ========================= function toast(msg){ const t = document.createElement('div'); t.textContent = msg; Object.assign(t.style, { position:'fixed', left:'50%', bottom:'28px', transform:'translateX(-50%)', background:'#0c0f16', color:'#cdd6e6', padding:'10px 14px', borderRadius:'999px', border:'1px solid #1e2432', boxShadow:'var(--shadow)', fontSize:'12px', zIndex:9999, opacity:0, transition:'opacity .2s, transform .2s' }); document.body.appendChild(t); requestAnimationFrame(()=>{ t.style.opacity = 1; t.style.transform = 'translateX(-50%) translateY(-4px)'; }); setTimeout(()=>{ t.style.opacity = 0; t.style.transform = 'translateX(-50%)'; setTimeout(()=> t.remove(), 180); }, 1600); } // ========================= Init ========================= function init(){ gradStart.value = '#40E0D0'; gradEnd.value = '#7C3AED'; updateGradient(); renderHistory(); state.h = 174; state.s = .72; state.v = .88; state.a = 1; bgInput.value = '#ffffff'; updateAll('init'); window.addEventListener('resize', updateRangeKnobs); } window.addEventListener('load', init);
Ultra Detailed Color Picker (Single File)

Ultra Detailed Color Picker

Drag, type, convert, copy — with palettes, contrast & gradients
Tip: you can type into HEX/RGB/HSL boxes — everything stays in sync.

Preview & Contrast

Aa
Lorem ipsum dolor sit amet — 16px body text.

Palettes (Tints/Shades) & Harmonies

Gradient Builder & History

Pro tips: Drag on the square to set Saturation (x) and Value/Brightness (y). Use Eyedrop to sample any on‑screen color (supported in most Chromium browsers). Background picker blends your current color over it for an accurate WCAG contrast (even when transparent). Click any swatch to copy its HEX.

${hex}`; node.appendChild(el); }); } function renderPalettes({r,g,b}){ // Tints/Shades via HSL lightness sweep const baseHsl = rgbToHsl(r,g,b); const steps = [5,10,15,25,35,50,65,75,85,92]; const ts = steps.map(L=>{ const {r:rr,g:gg,b:bb} = hslToRgb(baseHsl.h, baseHsl.s, L/100); return toHex({r:rr,g:gg,b:bb,a:1}, false); }); renderSwatches(tintsShades, ts); // Analogous: h ±30, ±60 const anHues = [-60,-30,0,30,60].map(d=> ((baseHsl.h + d + 360) % 360)); const analogousArr = anHues.map(h=>{ const {r:rr,g:gg,b:bb} = hslToRgb(h, baseHsl.s, baseHsl.l); return toHex({r:rr,g:gg,b:bb,a:1}, false); }); renderSwatches(analogous, analogousArr); // Complementary & Split Complementary (±150, ±210) const comp = (baseHsl.h + 180) % 360; const split1 = (baseHsl.h + 150) % 360; const split2 = (baseHsl.h + 210) % 360; const comps = [baseHsl.h, split1, comp, split2].map(h=>{ const {r:rr,g:gg,b:bb} = hslToRgb(h, baseHsl.s, baseHsl.l); return toHex({r:rr,g:gg,b:bb,a:1}, false); }); renderSwatches(complements, comps); // Triad (±120) & Tetrad (±90, ±180, ±270) const tri = [(baseHsl.h+0)%360, (baseHsl.h+120)%360, (baseHsl.h+240)%360]; const tet = [(baseHsl.h+0)%360, (baseHsl.h+90)%360, (baseHsl.h+180)%360, (baseHsl.h+270)%360]; const triT = [...tri, ...tet.filter(h=>!tri.includes(h))].map(h=>{ const {r:rr,g:gg,b:bb} = hslToRgb(h, baseHsl.s, baseHsl.l); return toHex({r:rr,g:gg,b:bb,a:1}, false); }); renderSwatches(triTetra, triT); } // ========================= Gradient Builder ========================= function updateGradient(){ const start = anyToRgba(gradStart.value) || anyToRgba(hexInput.value); const end = anyToRgba(gradEnd.value) || {r:0,g:0,b:0,a:1}; const a = clamp(Number(gradAngle.value)||0, 0, 360); const css = `linear-gradient(${a}deg, ${fmtRGB(start.r,start.g,start.b,start.a)} 0%, ${fmtRGB(end.r,end.g,end.b,end.a)} 100%)`; gradPreview.firstElementChild.style.background = css; gradCss.value = `background: ${css};`; } gradStart.addEventListener('change', updateGradient); gradEnd.addEventListener('change', updateGradient); gradAngle.addEventListener('input', updateGradient); copyGradient.addEventListener('click', ()=> copyText(gradCss.value)); // ========================= History (LocalStorage) ========================= const HISTORY_KEY = 'ultraColorPickerHistory'; function getHistory(){ try{ return JSON.parse(localStorage.getItem(HISTORY_KEY)||'[]'); }catch{ return []; } } function setHistory(arr){ localStorage.setItem(HISTORY_KEY, JSON.stringify(arr.slice(0, 24))); } function renderHistory(){ const arr = getHistory(); historyWrap.innerHTML = ''; arr.forEach(hex=>{ const el = document.createElement('div'); el.className = 'swatch'; el.innerHTML = `
${hex}`; el.addEventListener('click', ()=>{ hexInput.value = hex; setFromHex(); }); historyWrap.appendChild(el); }); } saveColor.addEventListener('click', ()=>{ const arr = getHistory(); if (!arr.includes(hexInput.value)) arr.unshift(hexInput.value); setHistory(arr); renderHistory(); toast('Saved to history'); }); clearHistoryBtn.addEventListener('click', ()=>{ setHistory([]); renderHistory(); }); // ========================= Export CSS Variables ========================= exportCss.addEventListener('click', ()=>{ const vars = [ `--color-current: ${rgbInput.value};`, `--color-current-hex: ${hexInput.value};` ].join('\n'); const css = `:root{\n${vars}\n}`; downloadText('color-vars.css', css); }); function downloadText(filename, content){ const blob = new Blob([content], {type:'text/css'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); setTimeout(()=> URL.revokeObjectURL(url), 1000); } // ========================= Randomize & Helpers ========================= randomize.addEventListener('click', ()=>{ state.h = Math.floor(Math.random()*360); state.s = Math.random(); state.v = lerp(0.5, 1, Math.random()); state.a = lerp(0.6, 1, Math.random()); updateAll('rand'); }); applyRecommend.addEventListener('click', ()=>{ const c = anyToRgba(recommendChip.textContent); if (c) setFromRgbObject({...c, a:1}); }); // Live update when background changes bgInput.addEventListener('change', ()=> updateAll('bg')); // ========================= Micro Toast ========================= function toast(msg){ const t = document.createElement('div'); t.textContent = msg; Object.assign(t.style, { position:'fixed', left:'50%', bottom:'28px', transform:'translateX(-50%)', background:'#0c0f16', color:'#cdd6e6', padding:'10px 14px', borderRadius:'999px', border:'1px solid #1e2432', boxShadow:'var(--shadow)', fontSize:'12px', zIndex:9999, opacity:0, transition:'opacity .2s, transform .2s' }); document.body.appendChild(t); requestAnimationFrame(()=>{ t.style.opacity = 1; t.style.transform = 'translateX(-50%) translateY(-4px)'; }); setTimeout(()=>{ t.style.opacity = 0; t.style.transform = 'translateX(-50%)'; setTimeout(()=> t.remove(), 180); }, 1600); } // ========================= Init ========================= function init(){ // Seed gradient inputs gradStart.value = '#40E0D0'; gradEnd.value = '#7C3AED'; updateGradient(); renderHistory(); // Start from teal state.h = 174; state.s = .72; state.v = .88; state.a = 1; bgInput.value = '#ffffff'; updateAll('init'); } window.addEventListener('load', init);