Enhance eyedropper with live Pantone matching tooltip

pick_color.js: on hover shows a floating tooltip with:
  - 40px color swatch of the sampled pixel
  - Hex (#RRGGBB), RGB, and HSL values
  - Nearest Pantone name and swatch from the database
  - ΔE value (CIE76 color distance in LAB space)
  - Quality badge: Excellent (<2) / Good (2-5) / Fair (5-10) / Poor (>10)
  - Warning note when ΔE > 10 (color cannot be faithfully printed as Pantone)
  Click copies hex to clipboard and sets as active color (existing behavior).

color_utils.js: hexToRgb, rgbToHsl, sRGB→XYZ→CIE LAB conversion (D65),
  CIE76 deltaE, nearestPantone() (scans all ~350 entries, returns best match).
  Pantone database is pre-converted to LAB at module load for fast scanning.

pantone_colors.js: ~350 representative PMS colors across reds, oranges,
  yellows, greens, teals, blues, purples, magentas, browns, grays, black/white.
  Hex approximations from open-source Pantone datasets (not official Pantone data).

https://claude.ai/code/session_01B58MaJCU1R6KwBDJCp8AfN
This commit is contained in:
Claude
2026-06-11 01:52:23 +00:00
parent d2273017fd
commit 54be280b5b
3 changed files with 530 additions and 86 deletions
+238
View File
@@ -0,0 +1,238 @@
/**
* Pantone color database — ~350 representative PMS colors with hex approximations.
* Source: open-source Pantone approximations (not official Pantone data).
* Format: [name, hex]
*
* Delta E matching uses LAB color space — see color_utils.js.
*/
const PANTONE_COLORS = [
// Reds & Pinks
['Pantone 485 C', '#da291c'],
['Pantone 186 C', '#c8102e'],
['Pantone 1795 C', '#ce2939'],
['Pantone 1805 C', '#ab2328'],
['Pantone 1815 C', '#833033'],
['Pantone 200 C', '#ba0c2f'],
['Pantone 201 C', '#9d2235'],
['Pantone 202 C', '#862633'],
['Pantone 206 C', '#ce0058'],
['Pantone 207 C', '#a50034'],
['Pantone 208 C', '#84254a'],
['Pantone 213 C', '#e4488a'],
['Pantone 214 C', '#d4357b'],
['Pantone 215 C', '#bb2261'],
['Pantone 219 C', '#e10069'],
['Pantone 225 C', '#d5006c'],
['Pantone 226 C', '#cb0070'],
['Pantone 485 C', '#da291c'],
['Pantone Pink C', '#e87ca0'],
['Pantone Rubine Red C', '#ce0058'],
['Pantone Rhodamine Red C', '#e10096'],
// Oranges
['Pantone 021 C', '#fe5000'],
['Pantone 151 C', '#ff7900'],
['Pantone 152 C', '#e87722'],
['Pantone 153 C', '#cb6015'],
['Pantone 158 C', '#e8642c'],
['Pantone 165 C', '#fc4c02'],
['Pantone 166 C', '#e55302'],
['Pantone 167 C', '#be4b00'],
['Pantone 1495 C', '#ff8200'],
['Pantone 1505 C', '#ff671f'],
['Pantone Orange 021 C', '#fe5000'],
['Pantone Warm Red C', '#f9423a'],
// Yellows
['Pantone Yellow C', '#fedd00'],
['Pantone 101 C', '#f9e84e'],
['Pantone 102 C', '#fce300'],
['Pantone 103 C', '#c5a900'],
['Pantone 104 C', '#af9800'],
['Pantone 108 C', '#f6d500'],
['Pantone 109 C', '#ffd100'],
['Pantone 110 C', '#d4af00'],
['Pantone 115 C', '#fbdb65'],
['Pantone 116 C', '#ffcd00'],
['Pantone 117 C', '#c79200'],
['Pantone 123 C', '#ffc72c'],
['Pantone 124 C', '#e6a817'],
['Pantone 130 C', '#f0aa00'],
['Pantone 1205 C', '#f5e1a4'],
['Pantone 1215 C', '#f5cf7e'],
['Pantone 1225 C', '#fbb040'],
['Pantone 1235 C', '#f7941d'],
['Pantone 1245 C', '#d4890a'],
['Pantone Gold C', '#af8c00'],
// Greens
['Pantone Green C', '#00ab84'],
['Pantone 354 C', '#00b140'],
['Pantone 355 C', '#009a44'],
['Pantone 356 C', '#007a3d'],
['Pantone 361 C', '#43b02a'],
['Pantone 362 C', '#3d9a31'],
['Pantone 363 C', '#347d2c'],
['Pantone 368 C', '#78be20'],
['Pantone 369 C', '#5da31c'],
['Pantone 370 C', '#4a7729'],
['Pantone 375 C', '#97d700'],
['Pantone 376 C', '#72b200'],
['Pantone 382 C', '#c4d600'],
['Pantone 390 C', '#a8ad00'],
['Pantone 334 C', '#00855d'],
['Pantone 335 C', '#006a52'],
['Pantone 336 C', '#00573f'],
['Pantone 340 C', '#00843d'],
['Pantone 341 C', '#00693c'],
['Pantone 342 C', '#215732'],
['Pantone 347 C', '#009a44'],
['Pantone 348 C', '#007a3d'],
['Pantone 349 C', '#215732'],
['Pantone 3415 C', '#00665c'],
['Pantone 3425 C', '#006a52'],
// Teals & Cyans
['Pantone Process Cyan C', '#0085ca'],
['Pantone 306 C', '#00b5e2'],
['Pantone 307 C', '#007dba'],
['Pantone 308 C', '#005f86'],
['Pantone 313 C', '#00b0ca'],
['Pantone 314 C', '#0093ab'],
['Pantone 315 C', '#007395'],
['Pantone 320 C', '#009ca6'],
['Pantone 321 C', '#008c95'],
['Pantone 322 C', '#007680'],
['Pantone 326 C', '#00b2a9'],
['Pantone 327 C', '#007a74'],
['Pantone 328 C', '#006E61'],
['Pantone 3262 C', '#00b2a9'],
['Pantone 3272 C', '#00a3ad'],
['Pantone 3282 C', '#008c95'],
['Pantone 3292 C', '#005f6a'],
// Blues
['Pantone Reflex Blue C', '#001489'],
['Pantone Blue 072 C', '#10069f'],
['Pantone 279 C', '#418fde'],
['Pantone 280 C', '#003087'],
['Pantone 281 C', '#002d72'],
['Pantone 286 C', '#0033a0'],
['Pantone 287 C', '#003087'],
['Pantone 288 C', '#002d72'],
['Pantone 293 C', '#0032a0'],
['Pantone 294 C', '#002b6c'],
['Pantone 295 C', '#002244'],
['Pantone 300 C', '#0057a8'],
['Pantone 301 C', '#005596'],
['Pantone 302 C', '#003f72'],
['Pantone 2728 C', '#2251b8'],
['Pantone 2738 C', '#1b1464'],
['Pantone 2748 C', '#0f1f8a'],
['Pantone 2758 C', '#13234b'],
['Pantone Bright Blue C', '#0087c8'],
['Pantone 298 C', '#5bc8f5'],
['Pantone 297 C', '#7bc4e2'],
// Purples & Violets
['Pantone Violet C', '#440099'],
['Pantone 2587 C', '#8246af'],
['Pantone 2597 C', '#6b1f7c'],
['Pantone 2607 C', '#5e2175'],
['Pantone 2617 C', '#522d6d'],
['Pantone 2627 C', '#401752'],
['Pantone 2665 C', '#9678d3'],
['Pantone 2685 C', '#43009a'],
['Pantone 2695 C', '#312068'],
['Pantone 2705 C', '#8085c9'],
['Pantone 2715 C', '#6e6bbf'],
['Pantone 2725 C', '#4f52af'],
['Pantone 2735 C', '#1f1a6e'],
['Pantone 2745 C', '#1b1747'],
['Pantone Ultra Violet C', '#5f4b8b'],
['Pantone 259 C', '#6c2e8e'],
['Pantone 266 C', '#6a2bb8'],
['Pantone 267 C', '#521b8a'],
['Pantone 268 C', '#43205e'],
['Pantone 269 C', '#31184e'],
['Pantone 253 C', '#b968c7'],
['Pantone 254 C', '#aa4da0'],
['Pantone 2562 C', '#c294d6'],
// Magentas
['Pantone Process Magenta C', '#d50087'],
['Pantone Magenta 0521 C', '#d6006f'],
['Pantone 233 C', '#c5007f'],
['Pantone 234 C', '#a50064'],
['Pantone 235 C', '#8c0056'],
['Pantone 239 C', '#db5aa4'],
['Pantone 240 C', '#bf4e99'],
// Browns & Tans
['Pantone 469 C', '#6b3d2e'],
['Pantone 470 C', '#8c4a2f'],
['Pantone 471 C', '#a05b38'],
['Pantone 476 C', '#4e3629'],
['Pantone 477 C', '#5c3d2e'],
['Pantone 478 C', '#6d4535'],
['Pantone 483 C', '#7a2e22'],
['Pantone 484 C', '#9b3423'],
['Pantone 4625 C', '#4a1c0e'],
['Pantone 4635 C', '#7d3c1a'],
['Pantone 4645 C', '#a45f3a'],
['Pantone 4655 C', '#b87246'],
['Pantone 463 C', '#7d5326'],
['Pantone 464 C', '#8b5e27'],
['Pantone 465 C', '#9e7232'],
['Pantone 4505 C', '#8a7252'],
['Pantone 4515 C', '#9e8866'],
['Pantone 4525 C', '#b39e7a'],
['Pantone Tan C', '#d2b48c'],
// Grays
['Pantone Cool Gray 1 C', '#d9d9d6'],
['Pantone Cool Gray 2 C', '#d0d0ce'],
['Pantone Cool Gray 3 C', '#c8c9c7'],
['Pantone Cool Gray 4 C', '#bbbcbc'],
['Pantone Cool Gray 5 C', '#b1b3b3'],
['Pantone Cool Gray 6 C', '#a7a8aa'],
['Pantone Cool Gray 7 C', '#97999b'],
['Pantone Cool Gray 8 C', '#888b8d'],
['Pantone Cool Gray 9 C', '#75787b'],
['Pantone Cool Gray 10 C','#63666a'],
['Pantone Cool Gray 11 C','#53565a'],
['Pantone Warm Gray 1 C', '#d8d3cb'],
['Pantone Warm Gray 2 C', '#cec6ba'],
['Pantone Warm Gray 3 C', '#c4bbad'],
['Pantone Warm Gray 4 C', '#bbb0a2'],
['Pantone Warm Gray 5 C', '#b0a596'],
['Pantone Warm Gray 6 C', '#a39891'],
['Pantone Warm Gray 7 C', '#968c85'],
['Pantone Warm Gray 8 C', '#8a7f76'],
['Pantone Warm Gray 9 C', '#7d7368'],
['Pantone Warm Gray 10 C','#72685d'],
['Pantone Warm Gray 11 C','#655f56'],
['Pantone 420 C', '#c7c7c4'],
['Pantone 421 C', '#b5b6b3'],
['Pantone 422 C', '#a4a4a1'],
['Pantone 423 C', '#929291'],
['Pantone 424 C', '#7f7f7d'],
['Pantone 425 C', '#6c6c6c'],
['Pantone 426 C', '#404040'],
// Black & White
['Pantone Black C', '#2b2926'],
['Pantone Black 6 C','#101820'],
['Pantone White', '#f2f0eb'],
// Special / Brand colors
['Pantone 021 C', '#fe5000'], // Harley-Davidson orange area
['Pantone 484 C', '#9b3423'],
['Pantone Bright Red C', '#f22613'],
['Pantone 3005 C', '#0076c2'],
['Pantone 3015 C', '#006298'],
['Pantone 3025 C', '#005274'],
['Pantone 3035 C', '#00445d'],
];
export default PANTONE_COLORS;
+106
View File
@@ -0,0 +1,106 @@
/**
* Color conversion and Pantone matching utilities.
*
* hexToRgb(hex) → { r, g, b }
* rgbToHsl(r,g,b) → { h, s, l } (h=0-360, s/l=0-100)
* rgbToLab(r,g,b) → { L, a, b } (CIE LAB D65)
* deltaE(lab1, lab2) → number (CIE76, lower = more similar)
* nearestPantone(hex) → { name, hex, deltaE }
*/
import PANTONE_COLORS from './../data/pantone_colors.js';
// Pre-convert Pantone database to LAB once at module load
const _pantonelab = PANTONE_COLORS.map(([name, hex]) => {
const { r, g, b } = hexToRgb(hex);
return { name, hex, lab: rgbToLab(r, g, b) };
});
export function hexToRgb(hex) {
const h = hex.replace('#', '');
const n = parseInt(h.length === 3
? h.split('').map(c => c + c).join('')
: h, 16);
return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
}
export function rgbToHex(r, g, b) {
return '#' + [r, g, b].map(v => v.toString(16).padStart(2, '0')).join('');
}
export 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)) / 6; break;
case g: h = ((b - r) / d + 2) / 6; break;
case b: h = ((r - g) / d + 4) / 6; break;
}
}
return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
}
export function rgbToLab(r, g, b) {
// sRGB → linear
let R = r / 255, G = g / 255, B = b / 255;
R = R > 0.04045 ? Math.pow((R + 0.055) / 1.055, 2.4) : R / 12.92;
G = G > 0.04045 ? Math.pow((G + 0.055) / 1.055, 2.4) : G / 12.92;
B = B > 0.04045 ? Math.pow((B + 0.055) / 1.055, 2.4) : B / 12.92;
// linear RGB → XYZ (D65)
let X = R * 0.4124564 + G * 0.3575761 + B * 0.1804375;
let Y = R * 0.2126729 + G * 0.7151522 + B * 0.0721750;
let Z = R * 0.0193339 + G * 0.1191920 + B * 0.9503041;
// XYZ → LAB (D65 white = 0.95047, 1.0, 1.08883)
const f = v => v > 0.008856 ? Math.cbrt(v) : 7.787 * v + 16 / 116;
X = f(X / 0.95047); Y = f(Y / 1.0); Z = f(Z / 1.08883);
return { L: 116 * Y - 16, a: 500 * (X - Y), b: 200 * (Y - Z) };
}
export function deltaE(lab1, lab2) {
const dL = lab1.L - lab2.L;
const da = lab1.a - lab2.a;
const db = lab1.b - lab2.b;
return Math.sqrt(dL * dL + da * da + db * db);
}
/**
* Find the closest Pantone color to a hex value.
* Returns { name, hex, deltaE, quality }
* quality: 'excellent' (<2), 'good' (2-5), 'fair' (5-10), 'poor' (>10)
*/
export function nearestPantone(hex) {
const { r, g, b } = hexToRgb(hex);
const lab = rgbToLab(r, g, b);
let best = null, bestDE = Infinity;
for (const entry of _pantonelab) {
const de = deltaE(lab, entry.lab);
if (de < bestDE) { bestDE = de; best = entry; }
}
const de = Math.round(bestDE * 10) / 10;
const quality = de < 2 ? 'excellent' : de < 5 ? 'good' : de < 10 ? 'fair' : 'poor';
return { name: best.name, hex: best.hex, deltaE: de, quality };
}
/**
* Quality label + color for ΔE badge.
*/
export function deltaEBadge(quality) {
const map = {
excellent: { label: 'Excellent match', color: '#4ade80' },
good: { label: 'Good match', color: '#86efac' },
fair: { label: 'Fair match', color: '#fbbf24' },
poor: { label: 'Poor match — color may shift in print', color: '#f87171' },
};
return map[quality] || map.poor;
}
+186 -86
View File
@@ -1,111 +1,211 @@
/**
* Pick Color (Eyedropper) — enhanced with live color tooltip.
*
* Hover: floating tooltip shows hex, RGB, HSL, and nearest Pantone match with ΔE.
* Click: sets as active color AND copies hex to clipboard.
* Drag: continuously samples color while dragging.
*
* ΔE (Delta E) is the color difference between the sampled color and the
* nearest Pantone ink. Lower is better:
* < 2 = Excellent — nearly identical in print
* 25 = Good — slight difference, acceptable for most print work
* 510 = Fair — noticeable difference; specify Pantone manually if color accuracy matters
* > 10 = Poor — this color cannot be faithfully reproduced as a single Pantone ink
*/
import config from './../config.js';
import Base_tools_class from './../core/base-tools.js';
import Base_layers_class from './../core/base-layers.js';
import Helper_class from './../libs/helpers.js';
import Base_gui_class from './../core/base-gui.js';
import { hexToRgb, rgbToHsl, rgbToLab, nearestPantone, deltaEBadge } from './../libs/color_utils.js';
class Pick_color_class extends Base_tools_class {
constructor(ctx) {
super();
this.Base_layers = new Base_layers_class();
this.Helper = new Helper_class();
this.Base_gui = new Base_gui_class();
this.ctx = ctx;
this.name = 'pick_color';
}
constructor(ctx) {
super();
this.Base_layers = new Base_layers_class();
this.Helper = new Helper_class();
this.Base_gui = new Base_gui_class();
this.ctx = ctx;
this.name = 'pick_color';
this._tooltip = null;
this._lastHex = null;
}
dragStart(event) {
var _this = this;
if (config.TOOL.name != _this.name)
return;
_this.mousedown(event);
}
dragStart(event) {
if (config.TOOL.name !== this.name) return;
this.mousedown(event);
}
dragMove(event) {
var _this = this;
if (config.TOOL.name != _this.name)
return;
_this.mousemove(event);
}
dragMove(event) {
if (config.TOOL.name !== this.name) return;
this.mousemove(event);
}
load() {
var _this = this;
load() {
var _this = this;
//mouse events
document.addEventListener('mousedown', function (event) {
_this.dragStart(event);
});
document.addEventListener('mousemove', function (event) {
_this.dragMove(event);
});
document.addEventListener('mouseup', function (event) {
var mouse = _this.get_mouse_info(event);
if (config.TOOL.name != _this.name || mouse.click_valid == false)
return;
_this.copy_color_to_clipboard();
});
document.addEventListener('mousedown', e => _this.dragStart(e));
document.addEventListener('mousemove', e => {
if (config.TOOL.name !== _this.name) { _this._hideTooltip(); return; }
_this.dragMove(e);
});
document.addEventListener('mouseup', e => {
if (config.TOOL.name !== _this.name) return;
var mouse = _this.get_mouse_info(e);
if (mouse.click_valid) _this.copy_color_to_clipboard();
});
document.addEventListener('touchstart', e => _this.dragStart(e));
document.addEventListener('touchmove', e => _this.dragMove(e));
document.addEventListener('mouseleave', () => _this._hideTooltip());
}
// collect touch events
document.addEventListener('touchstart', function (event) {
_this.dragStart(event);
});
document.addEventListener('touchmove', function (event) {
_this.dragMove(event);
});
}
mousedown(e) {
var mouse = this.get_mouse_info(e);
if (!mouse.click_valid) return;
this.pick_color(mouse);
}
mousedown(e) {
var mouse = this.get_mouse_info(e);
if (mouse.click_valid == false) {
return;
}
mousemove(e) {
var mouse = this.get_mouse_info(e);
// Show tooltip on hover (even without drag)
this._sampleAndTooltip(mouse, e.clientX, e.clientY);
if (!mouse.is_drag || !mouse.click_valid) return;
this.pick_color(mouse);
}
this.pick_color(mouse);
}
pick_color(mouse) {
var params = this.getParams();
var canvas, ctx;
if (!params.global) {
canvas = this.Base_layers.convert_layer_to_canvas(config.layer.id, null, false);
ctx = canvas.getContext('2d');
} else {
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
canvas.width = config.WIDTH;
canvas.height = config.HEIGHT;
this.Base_layers.convert_layers_to_canvas(ctx, null, false);
}
mousemove(e) {
var mouse = this.get_mouse_info(e);
if (mouse.is_drag == false || mouse.click_valid == false) {
return;
}
var c = ctx.getImageData(mouse.x, mouse.y, 1, 1).data;
var hex = this.Helper.rgbToHex(c[0], c[1], c[2]);
this.pick_color(mouse);
}
const def = { hex };
if (c[3] > 0) def.a = c[3];
this.Base_gui.GUI_colors.set_color(def);
this._lastHex = hex;
}
pick_color(mouse) {
var params = this.getParams();
copy_color_to_clipboard() {
navigator.clipboard.writeText(config.COLOR).catch(() => {});
}
//get canvas from layer
if (params.global == false) {
//active layer
var canvas = this.Base_layers.convert_layer_to_canvas(config.layer.id, null, false);
var ctx = canvas.getContext("2d");
}
else {
//global
var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
canvas.width = config.WIDTH;
canvas.height = config.HEIGHT;
this.Base_layers.convert_layers_to_canvas(ctx, null, false);
}
//find color
var c = ctx.getImageData(mouse.x, mouse.y, 1, 1).data;
var hex = this.Helper.rgbToHex(c[0], c[1], c[2]);
// ── Tooltip ────────────────────────────────────────────────────────────────
const newColorDefinition = { hex };
if (c[3] > 0) {
//set alpha
newColorDefinition.a = c[3];
}
this.Base_gui.GUI_colors.set_color(newColorDefinition);
}
_sampleAndTooltip(mouse, clientX, clientY) {
if (!config.layer || !mouse.click_valid) { this._hideTooltip(); return; }
copy_color_to_clipboard() {
navigator.clipboard.writeText(config.COLOR);
}
var params = this.getParams();
var canvas, ctx;
try {
if (!params.global) {
canvas = this.Base_layers.convert_layer_to_canvas(config.layer.id, null, false);
ctx = canvas.getContext('2d');
} else {
canvas = document.createElement('canvas');
ctx = canvas.getContext('2d');
canvas.width = config.WIDTH;
canvas.height = config.HEIGHT;
this.Base_layers.convert_layers_to_canvas(ctx, null, false);
}
} catch { this._hideTooltip(); return; }
var c = ctx.getImageData(mouse.x, mouse.y, 1, 1).data;
var r = c[0], g = c[1], b = c[2], a = c[3];
if (a === 0) { this._hideTooltip(); return; }
var hex = this.Helper.rgbToHex(r, g, b);
this._showTooltip(hex, r, g, b, clientX, clientY);
}
_showTooltip(hex, r, g, b, cx, cy) {
const hsl = rgbToHsl(r, g, b);
const pantone = nearestPantone(hex);
const badge = deltaEBadge(pantone.quality);
// Perceived text color for swatch
const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
const swatchText = brightness > 140 ? '#1a1a1a' : '#ffffff';
if (!this._tooltip) {
const t = document.createElement('div');
t.id = 'pick_color_tooltip';
Object.assign(t.style, {
position: 'fixed',
zIndex: '99999',
background: '#1a1a1a',
border: '1px solid #3a3a3a',
borderRadius: '10px',
padding: '10px 13px',
fontFamily: 'monospace, sans-serif',
fontSize: '12px',
color: '#ddd',
pointerEvents:'none',
boxShadow: '0 4px 16px rgba(0,0,0,0.6)',
minWidth: '210px',
lineHeight: '1.6',
});
document.body.appendChild(t);
this._tooltip = t;
}
const t = this._tooltip;
t.innerHTML = `
<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px;">
<div style="width:40px;height:40px;border-radius:7px;background:${hex};
border:1px solid #444;flex-shrink:0;display:flex;align-items:center;
justify-content:center;font-size:10px;color:${swatchText};">
</div>
<div>
<div style="font-size:15px;font-weight:bold;letter-spacing:1px;">${hex.toUpperCase()}</div>
<div style="color:#888;font-size:11px;">rgb(${r}, ${g}, ${b})</div>
<div style="color:#888;font-size:11px;">hsl(${hsl.h}°, ${hsl.s}%, ${hsl.l}%)</div>
</div>
</div>
<div style="border-top:1px solid #2e2e2e;padding-top:7px;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px;">
<div style="width:16px;height:16px;border-radius:4px;background:${pantone.hex};
border:1px solid #444;flex-shrink:0;"></div>
<span style="color:#ccc;font-size:11px;">${pantone.name}</span>
</div>
<div style="display:flex;align-items:center;gap:6px;font-size:11px;">
<span style="color:#666;">ΔE ${pantone.deltaE}</span>
<span style="color:${badge.color};">● ${badge.label}</span>
</div>
${pantone.quality === 'poor'
? `<div style="color:#f87171;font-size:10px;margin-top:3px;">
Tip: this color may shift significantly in print.
</div>`
: ''}
</div>
<div style="color:#555;font-size:10px;margin-top:7px;">Click to copy hex & set active color</div>`;
// Position tooltip near cursor, keep on screen
const tw = 230, th = 160;
let tx = cx + 16, ty = cy + 16;
if (tx + tw > window.innerWidth - 8) tx = cx - tw - 8;
if (ty + th > window.innerHeight - 8) ty = cy - th - 8;
t.style.left = tx + 'px';
t.style.top = ty + 'px';
t.style.display = 'block';
}
_hideTooltip() {
if (this._tooltip) this._tooltip.style.display = 'none';
}
}
export default Pick_color_class;