c+c).join('');
if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
return { r: parseInt(h.slice(0,2),16), g: parseInt(h.slice(2,4),16), b: parseInt(h.slice(4,6),16) };
},
get hsl() {
const c = this.rgb; if (!c) return null;
let r = c.r/255, g = c.g/255, b = c.b/255;
const max = Math.max(r,g,b), min = Math.min(r,g,b);
let h = 0, s = 0, l = (max+min)/2;
if (max !== min) {
const d = max-min;
s = l > 0.5 ? d/(2-max-min) : d/(max+min);
if (max===r) h=(g-b)/d+(b>g?6:0);
else if (max===g) h=(b-r)/d+2;
else h=(r-g)/d+4;
h *= 60;
}
return { h: Math.round(h), s: Math.round(s*100), l: Math.round(l*100) };
},
get cmyk() {
const c = this.rgb; if (!c) return null;
let r=c.r/255, g=c.g/255, b=c.b/255;
const k = 1-Math.max(r,g,b);
if (k===1) return { c:0, m:0, y:0, k:100 };
return { c:Math.round((1-r-k)/(1-k)*100), m:Math.round((1-g-k)/(1-k)*100), y:Math.round((1-b-k)/(1-k)*100), k:Math.round(k*100) };
},
get hexStr() { return this.hex.toUpperCase(); },
get rgbStr() { const c=this.rgb; return c ? `rgb(${c.r}, ${c.g}, ${c.b})` : ''; },
get hslStr() { const c=this.hsl; return c ? `hsl(${c.h}, ${c.s}%, ${c.l}%)` : ''; },
get cmykStr() { const c=this.cmyk; return c ? `cmyk(${c.c}%, ${c.m}%, ${c.y}%, ${c.k}%)` : ''; },
rgbToHex(r, g, b) {
return '#' + [r,g,b].map(v => Math.min(255,Math.max(0,parseInt(v)||0)).toString(16).padStart(2,'0')).join('');
},
setFromRgb(r, g, b) { this.hex = this.rgbToHex(r, g, b); },
// ── custom HSV picker ──
hsvToRgb(h, s, v) {
s/=100; v/=100;
const c = v*s, x = c*(1-Math.abs((h/60)%2-1)), m = v-c;
let r,g,b;
if (h<60) { r=c; g=x; b=0; }
else if (h<120){ r=x; g=c; b=0; }
else if (h<180){ r=0; g=c; b=x; }
else if (h<240){ r=0; g=x; b=c; }
else if (h<300){ r=x; g=0; b=c; }
else { r=c; g=0; b=x; }
return { r: Math.round((r+m)*255), g: Math.round((g+m)*255), b: Math.round((b+m)*255) };
},
rgbToHsv(r, g, b) {
r/=255; g/=255; b/=255;
const max = Math.max(r,g,b), min = Math.min(r,g,b), d = max-min;
let h = 0;
if (d) {
if (max===r) h = ((g-b)/d) % 6;
else if (max===g) h = (b-r)/d + 2;
else h = (r-g)/d + 4;
h *= 60; if (h<0) h += 360;
}
return { h: Math.round(h), s: Math.round(max ? d/max*100 : 0), v: Math.round(max*100) };
},
applyHsv() { const c = this.hsvToRgb(this.h, this.s, this.v); this.hex = this.rgbToHex(c.r, c.g, c.b); },
syncFromHex() { const c = this.rgb; if (!c) return; const hsv = this.rgbToHsv(c.r, c.g, c.b); this.h = hsv.h; this.s = hsv.s; this.v = hsv.v; },
moveSV(e) {
const r = this.$refs.sv.getBoundingClientRect();
let x = (e.clientX - r.left) / r.width, y = (e.clientY - r.top) / r.height;
this.s = Math.round(Math.min(1, Math.max(0, x)) * 100);
this.v = Math.round((1 - Math.min(1, Math.max(0, y))) * 100);
this.applyHsv();
},
moveHue(e) {
const r = this.$refs.hue.getBoundingClientRect();
let x = (e.clientX - r.left) / r.width;
this.h = Math.round(Math.min(1, Math.max(0, x)) * 360);
this.applyHsv();
},
async eyeDrop() {
if (!this.hasEyeDropper) return;
try { const r = await (new EyeDropper()).open(); this.hex = r.sRGBHex; this.syncFromHex(); } catch(e) {}
},
copyValue(key, val) {
navigator.clipboard.writeText(val);
this.copiedKey = key;
setTimeout(() => this.copiedKey = '', 1500);
}
}"
x-init="syncFromHex(); $watch('hex', () => { if (!dragging) syncFromHex(); })"
@pointermove.window="dragging === 'sv' ? moveSV($event) : dragging === 'hue' ? moveHue($event) : null"
@pointerup.window="dragging = null">
Farb-Umrechner
Farbe als Hex-Code eingeben, RGB-Werte setzen oder den Farbpicker nutzen — HEX, RGB, HSL und CMYK werden live angezeigt.