| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
var _typoKeys = { |
| 5 |
family:'font-family', weight:'font-weight', style:'font-style', |
| 6 |
transform:'text-transform', decoration:'text-decoration', |
| 7 |
sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m', |
| 8 |
lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m', |
| 9 |
letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m', |
| 10 |
wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m' |
| 11 |
}; |
| 12 |
var _typoUnits = { size:'sizeUnit', lineHeight:'lineHeightUnit', letterSpacing:'letterSpacingUnit', wordSpacing:'wordSpacingUnit' }; |
| 13 |
var _typoUnitDefaults = { size:'px', lineHeight:'', letterSpacing:'px', wordSpacing:'px' }; |
| 14 |
function typoCssVarsForEl(el, obj, prefix) { |
| 15 |
if (!obj || typeof obj !== 'object') return; |
| 16 |
Object.keys(_typoKeys).forEach(function (k) { |
| 17 |
var v = obj[k]; if (v === undefined || v === '') return; |
| 18 |
var prop = _typoKeys[k]; |
| 19 |
var base = k.replace(/Desktop|Tablet|Mobile/, ''); |
| 20 |
var uKey = _typoUnits[base]; |
| 21 |
if (uKey && typeof v === 'number') v = v + (obj[uKey] || _typoUnitDefaults[base] || ''); |
| 22 |
el.style.setProperty(prefix + prop, v); |
| 23 |
}); |
| 24 |
} |
| 25 |
|
| 26 |
// ── Colour utilities ───────────────────────────────────────────── |
| 27 |
function rgbToHex(r, g, b) { |
| 28 |
return '#' + [r, g, b].map(function (v) { |
| 29 |
return ('0' + Math.round(v).toString(16)).slice(-2); |
| 30 |
}).join(''); |
| 31 |
} |
| 32 |
|
| 33 |
function rgbToHsl(r, g, b) { |
| 34 |
r /= 255; g /= 255; b /= 255; |
| 35 |
var max = Math.max(r, g, b), min = Math.min(r, g, b); |
| 36 |
var h, s, l = (max + min) / 2; |
| 37 |
if (max === min) { h = s = 0; } |
| 38 |
else { |
| 39 |
var d = max - min; |
| 40 |
s = l > 0.5 ? d / (2 - max - min) : d / (max + min); |
| 41 |
switch (max) { |
| 42 |
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break; |
| 43 |
case g: h = ((b - r) / d + 2) / 6; break; |
| 44 |
default: h = ((r - g) / d + 4) / 6; break; |
| 45 |
} |
| 46 |
} |
| 47 |
return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)]; |
| 48 |
} |
| 49 |
|
| 50 |
// ── K-means palette extraction ─────────────────────────────────── |
| 51 |
function extractPalette(img, numColors, quality) { |
| 52 |
var canvas = document.createElement('canvas'); |
| 53 |
var scale = Math.min(1, 150 / Math.max(img.naturalWidth, img.naturalHeight, 1)); |
| 54 |
canvas.width = Math.max(1, Math.floor(img.naturalWidth * scale)); |
| 55 |
canvas.height = Math.max(1, Math.floor(img.naturalHeight * scale)); |
| 56 |
var ctx = canvas.getContext('2d'); |
| 57 |
ctx.drawImage(img, 0, 0, canvas.width, canvas.height); |
| 58 |
var data; |
| 59 |
try { data = ctx.getImageData(0, 0, canvas.width, canvas.height).data; } |
| 60 |
catch (e) { return null; } // CORS |
| 61 |
|
| 62 |
var pixels = []; |
| 63 |
for (var i = 0; i < data.length; i += 4 * (quality || 10)) { |
| 64 |
if (data[i + 3] < 128) continue; |
| 65 |
pixels.push([data[i], data[i + 1], data[i + 2]]); |
| 66 |
} |
| 67 |
if (pixels.length < numColors) return null; |
| 68 |
|
| 69 |
// K-means |
| 70 |
var k = Math.min(numColors, pixels.length); |
| 71 |
var centroids = []; |
| 72 |
for (var ci = 0; ci < k; ci++) { |
| 73 |
var idx = Math.floor((ci / k) * pixels.length); |
| 74 |
centroids.push(pixels[idx].slice()); |
| 75 |
} |
| 76 |
|
| 77 |
for (var iter = 0; iter < 8; iter++) { |
| 78 |
var clusters = centroids.map(function () { return []; }); |
| 79 |
pixels.forEach(function (p) { |
| 80 |
var best = 0, bestD = Infinity; |
| 81 |
centroids.forEach(function (c, j) { |
| 82 |
var d = (p[0]-c[0])*(p[0]-c[0]) + (p[1]-c[1])*(p[1]-c[1]) + (p[2]-c[2])*(p[2]-c[2]); |
| 83 |
if (d < bestD) { bestD = d; best = j; } |
| 84 |
}); |
| 85 |
clusters[best].push(p); |
| 86 |
}); |
| 87 |
var changed = false; |
| 88 |
clusters.forEach(function (cl, j) { |
| 89 |
if (!cl.length) return; |
| 90 |
var nr = cl.reduce(function (s, p) { return s + p[0]; }, 0) / cl.length; |
| 91 |
var ng = cl.reduce(function (s, p) { return s + p[1]; }, 0) / cl.length; |
| 92 |
var nb = cl.reduce(function (s, p) { return s + p[2]; }, 0) / cl.length; |
| 93 |
if (Math.abs(nr - centroids[j][0]) + Math.abs(ng - centroids[j][1]) + Math.abs(nb - centroids[j][2]) > 1) changed = true; |
| 94 |
centroids[j] = [nr, ng, nb]; |
| 95 |
}); |
| 96 |
if (!changed) break; |
| 97 |
} |
| 98 |
|
| 99 |
return centroids.map(function (c) { |
| 100 |
return { r: Math.round(c[0]), g: Math.round(c[1]), b: Math.round(c[2]) }; |
| 101 |
}); |
| 102 |
} |
| 103 |
|
| 104 |
// ── Copy to clipboard helper ───────────────────────────────────── |
| 105 |
function copyText(txt) { |
| 106 |
if (navigator.clipboard && navigator.clipboard.writeText) { |
| 107 |
navigator.clipboard.writeText(txt).catch(function () {}); |
| 108 |
} else { |
| 109 |
var ta = document.createElement('textarea'); |
| 110 |
ta.value = txt; ta.style.position = 'fixed'; ta.style.opacity = '0'; |
| 111 |
document.body.appendChild(ta); ta.select(); |
| 112 |
try { document.execCommand('copy'); } catch (e) {} |
| 113 |
document.body.removeChild(ta); |
| 114 |
} |
| 115 |
} |
| 116 |
|
| 117 |
// ── Init one block ─────────────────────────────────────────────── |
| 118 |
function initColorThief(appEl) { |
| 119 |
var raw = appEl.dataset.opts; |
| 120 |
if (!raw) return; |
| 121 |
var opts; |
| 122 |
try { opts = JSON.parse(raw); } catch (e) { return; } |
| 123 |
|
| 124 |
// Set typography CSS vars on the parent wrapper |
| 125 |
var parentWrap = appEl.closest('.wp-block-blockenberg-color-thief') || appEl.parentElement; |
| 126 |
if (parentWrap) { |
| 127 |
typoCssVarsForEl(parentWrap, opts.typoLabel, '--bkbg-cth-lb-'); |
| 128 |
} |
| 129 |
|
| 130 |
var size = opts.swatchSize || 60; |
| 131 |
var radius = opts.swatchRadius || 8; |
| 132 |
var gap = opts.swatchGap || 8; |
| 133 |
var numColors = opts.paletteSize || 6; |
| 134 |
var quality = opts.quality || 10; |
| 135 |
var layout = opts.layout || 'row'; |
| 136 |
var copyFmt = opts.copyFormat || 'hex'; |
| 137 |
var padding = opts.padding || 20; |
| 138 |
var domRadius = opts.borderRadius || 12; |
| 139 |
|
| 140 |
// Build containing block |
| 141 |
var block = document.createElement('div'); |
| 142 |
block.style.backgroundColor = opts.bgColor || '#ffffff'; |
| 143 |
block.style.borderRadius = domRadius + 'px'; |
| 144 |
block.style.border = '1px solid ' + (opts.borderColor || '#e5e7eb'); |
| 145 |
block.style.padding = padding + 'px'; |
| 146 |
block.style.overflow = 'hidden'; |
| 147 |
|
| 148 |
// Image preview |
| 149 |
var imgEl = null; |
| 150 |
if (opts.showImagePreview && opts.imageUrl) { |
| 151 |
var imgWrap = document.createElement('div'); |
| 152 |
imgWrap.className = 'bkbg-ct-image-wrap'; |
| 153 |
imgWrap.style.borderRadius = Math.max(0, domRadius - 4) + 'px'; |
| 154 |
imgEl = document.createElement('img'); |
| 155 |
imgEl.src = opts.imageUrl; |
| 156 |
imgEl.alt = opts.imageAlt || ''; |
| 157 |
imgEl.crossOrigin = 'anonymous'; |
| 158 |
imgEl.style.height = (opts.imageHeight || 200) + 'px'; |
| 159 |
imgEl.style.objectFit = opts.imageObjectFit || 'cover'; |
| 160 |
imgWrap.appendChild(imgEl); |
| 161 |
block.appendChild(imgWrap); |
| 162 |
} |
| 163 |
|
| 164 |
var placeholderEl = null; |
| 165 |
if (!opts.imageUrl) { |
| 166 |
placeholderEl = document.createElement('div'); |
| 167 |
placeholderEl.style.cssText = 'padding:24px;text-align:center;color:#9ca3af;'; |
| 168 |
placeholderEl.textContent = 'No image selected.'; |
| 169 |
block.appendChild(placeholderEl); |
| 170 |
} |
| 171 |
|
| 172 |
appEl.parentNode.replaceChild(block, appEl); |
| 173 |
if (!opts.imageUrl) return; |
| 174 |
|
| 175 |
// Process after image loads |
| 176 |
function process(img) { |
| 177 |
var palette = extractPalette(img, numColors, quality); |
| 178 |
if (!palette || !palette.length) { |
| 179 |
var errEl = document.createElement('div'); |
| 180 |
errEl.style.cssText = 'color:#ef4444;font-size:13px;padding:8px;'; |
| 181 |
errEl.textContent = '⚠ Could not extract palette. Image may have CORS restrictions.'; |
| 182 |
block.appendChild(errEl); |
| 183 |
return; |
| 184 |
} |
| 185 |
|
| 186 |
// Dominant colour banner |
| 187 |
if (opts.showDominant) { |
| 188 |
var dom = palette[0]; |
| 189 |
var domEl = document.createElement('div'); |
| 190 |
domEl.className = 'bkbg-ct-dominant'; |
| 191 |
domEl.style.background = rgbToHex(dom.r, dom.g, dom.b); |
| 192 |
domEl.textContent = 'DOMINANT: ' + rgbToHex(dom.r, dom.g, dom.b).toUpperCase(); |
| 193 |
block.appendChild(domEl); |
| 194 |
} |
| 195 |
|
| 196 |
// Swatch container |
| 197 |
var swatchesEl = document.createElement('div'); |
| 198 |
swatchesEl.className = 'bkbg-ct-swatches' + (layout === 'list' ? ' bkbg-ct-list' : ''); |
| 199 |
if (layout === 'grid') { |
| 200 |
swatchesEl.style.display = 'grid'; |
| 201 |
swatchesEl.style.gridTemplateColumns = 'repeat(' + (opts.swatchColumns || 6) + ', 1fr)'; |
| 202 |
swatchesEl.style.gap = gap + 'px'; |
| 203 |
} else if (layout === 'list') { |
| 204 |
swatchesEl.style.display = 'flex'; |
| 205 |
swatchesEl.style.flexDirection = 'column'; |
| 206 |
swatchesEl.style.gap = gap + 'px'; |
| 207 |
} else { |
| 208 |
swatchesEl.style.display = 'flex'; |
| 209 |
swatchesEl.style.flexWrap = 'wrap'; |
| 210 |
swatchesEl.style.gap = gap + 'px'; |
| 211 |
swatchesEl.style.justifyContent = 'center'; |
| 212 |
} |
| 213 |
|
| 214 |
palette.forEach(function (c) { |
| 215 |
var hex = rgbToHex(c.r, c.g, c.b); |
| 216 |
var rgb = [c.r, c.g, c.b]; |
| 217 |
var hsl = rgbToHsl(c.r, c.g, c.b); |
| 218 |
var hexLabel = hex.toUpperCase(); |
| 219 |
var rgbLabel = 'rgb(' + rgb.join(', ') + ')'; |
| 220 |
var hslLabel = 'hsl(' + hsl[0] + ', ' + hsl[1] + '%, ' + hsl[2] + '%)'; |
| 221 |
var copyValue = copyFmt === 'rgb' ? rgbLabel : copyFmt === 'hsl' ? hslLabel : hexLabel; |
| 222 |
var displayLabel = opts.showHexValues ? hexLabel : opts.showRgbValues ? rgbLabel : opts.showHslValues ? hslLabel : ''; |
| 223 |
|
| 224 |
if (layout === 'list') { |
| 225 |
var item = document.createElement('div'); |
| 226 |
item.className = 'bkbg-ct-list-item'; |
| 227 |
|
| 228 |
var chip = document.createElement('div'); |
| 229 |
chip.className = 'bkbg-ct-swatch-color'; |
| 230 |
chip.style.width = '40px'; chip.style.height = '40px'; |
| 231 |
chip.style.borderRadius = radius + 'px'; |
| 232 |
chip.style.background = hex; |
| 233 |
chip.style.flexShrink = '0'; |
| 234 |
item.appendChild(chip); |
| 235 |
|
| 236 |
var info = document.createElement('div'); |
| 237 |
info.className = 'bkbg-ct-list-info'; |
| 238 |
info.style.color = opts.labelColor || '#374151'; |
| 239 |
if (opts.showHexValues) { var hl = document.createElement('span'); hl.style.fontFamily = 'monospace'; hl.textContent = hexLabel; info.appendChild(hl); } |
| 240 |
if (opts.showRgbValues) { var rl = document.createElement('span'); rl.style.fontFamily = 'monospace'; rl.textContent = rgbLabel; info.appendChild(rl); } |
| 241 |
if (opts.showHslValues) { var sl = document.createElement('span'); sl.style.fontFamily = 'monospace'; sl.textContent = hslLabel; info.appendChild(sl); } |
| 242 |
item.appendChild(info); |
| 243 |
|
| 244 |
if (opts.showCopyButton) { |
| 245 |
var btn = document.createElement('button'); |
| 246 |
btn.className = 'bkbg-ct-copy-btn'; |
| 247 |
btn.style.color = opts.labelColor || '#374151'; |
| 248 |
btn.textContent = 'Copy'; |
| 249 |
btn.addEventListener('click', function () { |
| 250 |
copyText(copyValue); |
| 251 |
btn.textContent = 'Copied!'; |
| 252 |
setTimeout(function () { btn.textContent = 'Copy'; }, 1500); |
| 253 |
}); |
| 254 |
item.appendChild(btn); |
| 255 |
} |
| 256 |
|
| 257 |
swatchesEl.appendChild(item); |
| 258 |
} else { |
| 259 |
var swatch = document.createElement('div'); |
| 260 |
swatch.className = 'bkbg-ct-swatch'; |
| 261 |
|
| 262 |
var colorBox = document.createElement('div'); |
| 263 |
colorBox.className = 'bkbg-ct-swatch-color' + (opts.showCopyButton ? ' bkbg-ct-copyable' : ''); |
| 264 |
colorBox.style.width = size + 'px'; |
| 265 |
colorBox.style.height = size + 'px'; |
| 266 |
colorBox.style.borderRadius = radius + 'px'; |
| 267 |
colorBox.style.background = hex; |
| 268 |
colorBox.title = copyValue; |
| 269 |
|
| 270 |
if (opts.showCopyButton) { |
| 271 |
colorBox.addEventListener('click', function () { |
| 272 |
copyText(copyValue); |
| 273 |
colorBox.classList.add('bkbg-ct-copied'); |
| 274 |
setTimeout(function () { colorBox.classList.remove('bkbg-ct-copied'); }, 1200); |
| 275 |
}); |
| 276 |
} |
| 277 |
|
| 278 |
swatch.appendChild(colorBox); |
| 279 |
|
| 280 |
if (displayLabel) { |
| 281 |
var labelEl = document.createElement('span'); |
| 282 |
labelEl.className = 'bkbg-ct-swatch-label'; |
| 283 |
labelEl.style.color = opts.labelColor || '#374151'; |
| 284 |
labelEl.style.maxWidth = size + 'px'; |
| 285 |
labelEl.textContent = displayLabel; |
| 286 |
swatch.appendChild(labelEl); |
| 287 |
} |
| 288 |
|
| 289 |
swatchesEl.appendChild(swatch); |
| 290 |
} |
| 291 |
}); |
| 292 |
|
| 293 |
block.appendChild(swatchesEl); |
| 294 |
} |
| 295 |
|
| 296 |
// Load image |
| 297 |
if (imgEl) { |
| 298 |
if (imgEl.complete && imgEl.naturalWidth > 0) { |
| 299 |
process(imgEl); |
| 300 |
} else { |
| 301 |
imgEl.addEventListener('load', function () { process(imgEl); }); |
| 302 |
imgEl.addEventListener('error', function () { |
| 303 |
// Try without crossOrigin for non-CORS servers |
| 304 |
var img2 = new Image(); |
| 305 |
img2.onload = function () { process(img2); }; |
| 306 |
img2.onerror = function () {}; |
| 307 |
img2.src = opts.imageUrl; |
| 308 |
}); |
| 309 |
} |
| 310 |
} else if (opts.imageUrl && !opts.showImagePreview) { |
| 311 |
// Load hidden canvas image for palette extraction only |
| 312 |
var hiddenImg = new Image(); |
| 313 |
hiddenImg.crossOrigin = 'anonymous'; |
| 314 |
hiddenImg.onload = function () { process(hiddenImg); }; |
| 315 |
hiddenImg.onerror = function () {}; |
| 316 |
hiddenImg.src = opts.imageUrl; |
| 317 |
} |
| 318 |
} |
| 319 |
|
| 320 |
document.addEventListener('DOMContentLoaded', function () { |
| 321 |
document.querySelectorAll('.bkbg-ct-app').forEach(initColorThief); |
| 322 |
}); |
| 323 |
|
| 324 |
if (document.readyState === 'complete' || document.readyState === 'interactive') { |
| 325 |
document.querySelectorAll('.bkbg-ct-app').forEach(initColorThief); |
| 326 |
} |
| 327 |
})(); |
| 328 |
|