class Label { constructor(gallery) { this.gallery = gallery this.overlayElement = null this.leftIndexElement = null this.wordElement = null this.chipElement = null this.cmykValueElement = null this.rgbValueElement = null this.hexValueElement = null this.pmsValueElement = null this.activePlaneIndex = -1 } createElement() { const element = document.createElement('section') element.className = 'plane-label-overlay' element.innerHTML = `

CMYK
RGB
HEX
PMS
` return { element, leftIndexElement: element.querySelector('.plane-label-overlay__index'), wordElement: element.querySelector('.plane-label-card__word'), chipElement: element.querySelector('.plane-label-overlay__chip'), cmykValueElement: element.querySelector('.plane-label-card__value--cmyk'), rgbValueElement: element.querySelector('.plane-label-card__value--rgb'), hexValueElement: element.querySelector('.plane-label-card__value--hex'), pmsValueElement: element.querySelector('.plane-label-card__value--pms'), } } init() { if (this.overlayElement) return const { element, leftIndexElement, wordElement, chipElement, cmykValueElement, rgbValueElement, hexValueElement, pmsValueElement, } = this.createElement() this.overlayElement = element this.leftIndexElement = leftIndexElement this.wordElement = wordElement this.chipElement = chipElement this.cmykValueElement = cmykValueElement this.rgbValueElement = rgbValueElement this.hexValueElement = hexValueElement this.pmsValueElement = pmsValueElement this.overlayElement.style.opacity = '0' document.body.append(this.overlayElement) } normalizeHexColor(rawColor) { const fallbackColor = '#ffffff' if (typeof rawColor !== 'string') return fallbackColor let hexColor = rawColor.trim() if (!hexColor) return fallbackColor if (!hexColor.startsWith('#')) { hexColor = `#${hexColor}` } if (/^#[0-9a-fA-F]{3}$/.test(hexColor)) { const shortHex = hexColor.slice(1) hexColor = `#${shortHex .split('') .map((character) => `${character}${character}`) .join('')}` } if (!/^#[0-9a-fA-F]{6}$/.test(hexColor)) return fallbackColor return hexColor.toLowerCase() } hexToRgb(hexColor) { const normalizedColor = this.normalizeHexColor(hexColor).slice(1) const red = Number.parseInt(normalizedColor.slice(0, 2), 16) const green = Number.parseInt(normalizedColor.slice(2, 4), 16) const blue = Number.parseInt(normalizedColor.slice(4, 6), 16) return { r: red, g: green, b: blue, } } rgbToCmyk({ r, g, b }) { const red = r / 255 const green = g / 255 const blue = b / 255 const black = 1 - Math.max(red, green, blue) if (black >= 0.999) { return { c: 0, m: 0, y: 0, k: 100 } } const cyan = ((1 - red - black) / (1 - black)) * 100 const magenta = ((1 - green - black) / (1 - black)) * 100 const yellow = ((1 - blue - black) / (1 - black)) * 100 return { c: Math.round(cyan), m: Math.round(magenta), y: Math.round(yellow), k: Math.round(black * 100), } } buildColorSpecs(accentColor, pmsValue) { const normalizedAccentColor = this.normalizeHexColor(accentColor) const rgb = this.hexToRgb(normalizedAccentColor) const cmyk = this.rgbToCmyk(rgb) return { chipHex: normalizedAccentColor, cmyk: `${cmyk.c}, ${cmyk.m}, ${cmyk.y}, ${cmyk.k}`, rgb: `${rgb.r}, ${rgb.g}, ${rgb.b}`, hex: normalizedAccentColor.slice(1).toUpperCase(), pms: pmsValue || 'N/A', } } getTargetPlaneIndex(cameraZ) { const blendData = this.gallery.getPlaneBlendData(cameraZ) if (!blendData) return -1 return blendData.blend >= 0.5 ? blendData.nextPlaneIndex : blendData.currentPlaneIndex } applyPlaneContent(planeIndex) { const plane = this.gallery.planes[planeIndex] if (!plane || this.activePlaneIndex === planeIndex) return const labelData = plane.userData.label || {} const colorSpecs = this.buildColorSpecs(plane.userData.accentColor, labelData.pms) this.leftIndexElement.textContent = String(planeIndex + 1).padStart(2, '0') this.wordElement.textContent = labelData.word || 'tone' this.chipElement.style.backgroundColor = colorSpecs.chipHex this.cmykValueElement.textContent = colorSpecs.cmyk this.rgbValueElement.textContent = colorSpecs.rgb this.hexValueElement.textContent = colorSpecs.hex this.pmsValueElement.textContent = colorSpecs.pms this.overlayElement.style.color = labelData.color || '' this.activePlaneIndex = planeIndex } resize() {} update(camera = null) { if (!camera || !this.overlayElement) return const targetPlaneIndex = this.getTargetPlaneIndex(camera.position.z) if (targetPlaneIndex < 0) { this.overlayElement.style.opacity = '0' return } this.applyPlaneContent(targetPlaneIndex) this.overlayElement.style.opacity = '1' } render() {} dispose() { this.overlayElement?.remove() this.overlayElement = null this.leftIndexElement = null this.wordElement = null this.chipElement = null this.cmykValueElement = null this.rgbValueElement = null this.hexValueElement = null this.pmsValueElement = null this.activePlaneIndex = -1 } } export { Label }