| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
/* ── Typography CSS-var helper ── */ |
| 5 |
var _typoKeys = { |
| 6 |
family:'font-family', weight:'font-weight', style:'font-style', |
| 7 |
transform:'text-transform', decoration:'text-decoration', |
| 8 |
sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m', |
| 9 |
lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m', |
| 10 |
letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m', |
| 11 |
wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m' |
| 12 |
}; |
| 13 |
var _typoUnits = { size:'sizeUnit', lineHeight:'lineHeightUnit', letterSpacing:'letterSpacingUnit', wordSpacing:'wordSpacingUnit' }; |
| 14 |
var _typoUnitDefaults = { size:'px', lineHeight:'', letterSpacing:'px', wordSpacing:'px' }; |
| 15 |
function typoCssVarsForEl(el, obj, prefix) { |
| 16 |
if (!obj || typeof obj !== 'object') return; |
| 17 |
Object.keys(_typoKeys).forEach(function (k) { |
| 18 |
var v = obj[k]; if (v === undefined || v === '') return; |
| 19 |
var prop = _typoKeys[k]; |
| 20 |
var base = k.replace(/Desktop|Tablet|Mobile/, ''); |
| 21 |
var uKey = _typoUnits[base]; |
| 22 |
if (uKey && typeof v === 'number') v = v + (obj[uKey] || _typoUnitDefaults[base] || ''); |
| 23 |
el.style.setProperty(prefix + prop, v); |
| 24 |
}); |
| 25 |
} |
| 26 |
|
| 27 |
/* ---- Color utilities ---- */ |
| 28 |
function hexToRgb(hex) { |
| 29 |
var c = (hex || '#000000').replace('#', ''); |
| 30 |
return [parseInt(c.slice(0,2),16), parseInt(c.slice(2,4),16), parseInt(c.slice(4,6),16)]; |
| 31 |
} |
| 32 |
|
| 33 |
function rgbToHex(r, g, b) { |
| 34 |
function h(x) { return Math.round(Math.min(255, Math.max(0, x))).toString(16).padStart(2,'0'); } |
| 35 |
return '#' + h(r) + h(g) + h(b); |
| 36 |
} |
| 37 |
|
| 38 |
function mixColors(hexA, hexB, t) { |
| 39 |
var a = hexToRgb(hexA), bv = hexToRgb(hexB); |
| 40 |
return rgbToHex(a[0]+(bv[0]-a[0])*t, a[1]+(bv[1]-a[1])*t, a[2]+(bv[2]-a[2])*t); |
| 41 |
} |
| 42 |
|
| 43 |
function rgbToHsl(r, g, b) { |
| 44 |
r /= 255; g /= 255; b /= 255; |
| 45 |
var max = Math.max(r,g,b), min = Math.min(r,g,b); |
| 46 |
var h, s, l = (max+min)/2; |
| 47 |
if (max === min) { h = s = 0; } |
| 48 |
else { |
| 49 |
var d = max - min; |
| 50 |
s = l > 0.5 ? d/(2-max-min) : d/(max+min); |
| 51 |
switch(max) { |
| 52 |
case r: h = (g-b)/d + (g<b?6:0); break; |
| 53 |
case g: h = (b-r)/d + 2; break; |
| 54 |
default: h = (r-g)/d + 4; |
| 55 |
} |
| 56 |
h /= 6; |
| 57 |
} |
| 58 |
return [Math.round(h*360), Math.round(s*100), Math.round(l*100)]; |
| 59 |
} |
| 60 |
|
| 61 |
function hslToRgb(h, s, l) { |
| 62 |
h /= 360; s /= 100; l /= 100; |
| 63 |
var r, g, b; |
| 64 |
if (s === 0) { r = g = b = l; } |
| 65 |
else { |
| 66 |
function hue2rgb(p, q, t) { |
| 67 |
if (t<0) t+=1; if (t>1) t-=1; |
| 68 |
if (t<1/6) return p+(q-p)*6*t; |
| 69 |
if (t<1/2) return q; |
| 70 |
if (t<2/3) return p+(q-p)*(2/3-t)*6; |
| 71 |
return p; |
| 72 |
} |
| 73 |
var q = l<0.5 ? l*(1+s) : l+s-l*s, p = 2*l-q; |
| 74 |
r = hue2rgb(p,q,h+1/3); g = hue2rgb(p,q,h); b = hue2rgb(p,q,h-1/3); |
| 75 |
} |
| 76 |
return [Math.round(r*255), Math.round(g*255), Math.round(b*255)]; |
| 77 |
} |
| 78 |
|
| 79 |
function hslHex(h, s, l) { |
| 80 |
var rgb = hslToRgb(h, s, l); |
| 81 |
return rgbToHex(rgb[0], rgb[1], rgb[2]); |
| 82 |
} |
| 83 |
|
| 84 |
function hexToHsl(hex) { |
| 85 |
var rgb = hexToRgb(hex); |
| 86 |
return rgbToHsl(rgb[0], rgb[1], rgb[2]); |
| 87 |
} |
| 88 |
|
| 89 |
function luminance(hex) { |
| 90 |
var rgb = hexToRgb(hex); |
| 91 |
return 0.299*rgb[0] + 0.587*rgb[1] + 0.114*rgb[2]; |
| 92 |
} |
| 93 |
function textOn(hex) { return luminance(hex) > 140 ? '#111827' : '#f9fafb'; } |
| 94 |
|
| 95 |
function getHarmonies(hex) { |
| 96 |
var hsl = hexToHsl(hex); |
| 97 |
var h = hsl[0], s = hsl[1], l = hsl[2]; |
| 98 |
return { |
| 99 |
complementary: [hslHex((h+180)%360, s, l)], |
| 100 |
analogous: [hslHex((h+30)%360, s, l), hslHex((h+330)%360, s, l)], |
| 101 |
triadic: [hslHex((h+120)%360, s, l), hslHex((h+240)%360, s, l)], |
| 102 |
split: [hslHex((h+150)%360, s, l), hslHex((h+210)%360, s, l)] |
| 103 |
}; |
| 104 |
} |
| 105 |
|
| 106 |
function copyText(text, el) { |
| 107 |
navigator.clipboard && navigator.clipboard.writeText(text).catch(function () { |
| 108 |
var t = document.createElement('textarea'); |
| 109 |
t.value = text; document.body.appendChild(t); t.select(); |
| 110 |
document.execCommand('copy'); document.body.removeChild(t); |
| 111 |
}); |
| 112 |
var orig = el.querySelector('.bkbg-cm-format-val'); |
| 113 |
if (orig) { var ov = orig.textContent; orig.textContent = 'Copied!'; setTimeout(function(){ orig.textContent = ov; }, 1500); } |
| 114 |
} |
| 115 |
|
| 116 |
function formatRgb(hex) { |
| 117 |
var c = hexToRgb(hex); |
| 118 |
return 'rgb(' + c[0] + ', ' + c[1] + ', ' + c[2] + ')'; |
| 119 |
} |
| 120 |
|
| 121 |
function formatHsl(hex) { |
| 122 |
var c = hexToHsl(hex); |
| 123 |
return 'hsl(' + c[0] + ', ' + c[1] + '%, ' + c[2] + '%)'; |
| 124 |
} |
| 125 |
|
| 126 |
function initBlock(root) { |
| 127 |
var opts; |
| 128 |
try { opts = JSON.parse(root.getAttribute('data-opts')); } catch (e) { return; } |
| 129 |
|
| 130 |
var a = opts; |
| 131 |
var colorA = a.colorA || '#6366f1'; |
| 132 |
var colorB = a.colorB || '#ec4899'; |
| 133 |
var ratio = (parseInt(a.defaultRatio) || 50) / 100; |
| 134 |
|
| 135 |
root.innerHTML = ''; |
| 136 |
|
| 137 |
var wrap = document.createElement('div'); |
| 138 |
wrap.className = 'bkbg-cm-wrap'; |
| 139 |
wrap.style.cssText = 'background:' + a.sectionBg + ';max-width:' + a.contentMaxWidth + 'px;margin:0 auto;'; |
| 140 |
root.appendChild(wrap); |
| 141 |
|
| 142 |
typoCssVarsForEl(wrap, a.typoTitle, '--bkbg-cm-tt-'); |
| 143 |
typoCssVarsForEl(wrap, a.typoSubtitle, '--bkbg-cm-st-'); |
| 144 |
|
| 145 |
if (a.showTitle) { |
| 146 |
var h = document.createElement('div'); |
| 147 |
h.className = 'bkbg-cm-title'; |
| 148 |
h.style.color = a.titleColor; |
| 149 |
h.textContent = a.title; |
| 150 |
wrap.appendChild(h); |
| 151 |
} |
| 152 |
|
| 153 |
if (a.showSubtitle) { |
| 154 |
var s = document.createElement('div'); |
| 155 |
s.className = 'bkbg-cm-subtitle'; |
| 156 |
s.style.color = a.subtitleColor; |
| 157 |
s.textContent = a.subtitle; |
| 158 |
wrap.appendChild(s); |
| 159 |
} |
| 160 |
|
| 161 |
/* -- Pickers card -- */ |
| 162 |
var pickCard = document.createElement('div'); |
| 163 |
pickCard.className = 'bkbg-cm-card'; |
| 164 |
pickCard.style.background = a.cardBg; |
| 165 |
var pickersRow = document.createElement('div'); |
| 166 |
pickersRow.className = 'bkbg-cm-pickers'; |
| 167 |
pickCard.appendChild(pickersRow); |
| 168 |
wrap.appendChild(pickCard); |
| 169 |
|
| 170 |
function makePickerCol(colorRef, label, onChange) { |
| 171 |
var col = document.createElement('div'); |
| 172 |
col.className = 'bkbg-cm-picker-col'; |
| 173 |
|
| 174 |
var swatchWrap = document.createElement('div'); |
| 175 |
swatchWrap.className = 'bkbg-cm-swatch'; |
| 176 |
swatchWrap.style.background = colorRef; |
| 177 |
|
| 178 |
var colorInp = document.createElement('input'); |
| 179 |
colorInp.type = 'color'; |
| 180 |
colorInp.value = colorRef; |
| 181 |
colorInp.addEventListener('input', function () { |
| 182 |
swatchWrap.style.background = colorInp.value; |
| 183 |
hexDisplay.textContent = colorInp.value.toUpperCase(); |
| 184 |
onChange(colorInp.value); |
| 185 |
renderAll(); |
| 186 |
}); |
| 187 |
swatchWrap.appendChild(colorInp); |
| 188 |
col.appendChild(swatchWrap); |
| 189 |
|
| 190 |
var hexDisplay = document.createElement('code'); |
| 191 |
hexDisplay.style.cssText = 'font-size:13px;color:' + a.labelColor + ';'; |
| 192 |
hexDisplay.textContent = colorRef.toUpperCase(); |
| 193 |
col.appendChild(hexDisplay); |
| 194 |
|
| 195 |
var lbl = document.createElement('div'); |
| 196 |
lbl.className = 'bkbg-cm-swatch-label'; |
| 197 |
lbl.style.color = a.subtitleColor; |
| 198 |
lbl.textContent = label; |
| 199 |
col.appendChild(lbl); |
| 200 |
|
| 201 |
return col; |
| 202 |
} |
| 203 |
|
| 204 |
var colA = makePickerCol(colorA, 'Color A', function (v) { colorA = v; }); |
| 205 |
var plusDiv = document.createElement('div'); |
| 206 |
plusDiv.className = 'bkbg-cm-plus'; |
| 207 |
plusDiv.style.color = a.labelColor; |
| 208 |
plusDiv.textContent = '+'; |
| 209 |
var colB = makePickerCol(colorB, 'Color B', function (v) { colorB = v; }); |
| 210 |
[colA, plusDiv, colB].forEach(function (el) { pickersRow.appendChild(el); }); |
| 211 |
|
| 212 |
/* -- Mix card -- */ |
| 213 |
var mixCard = document.createElement('div'); |
| 214 |
mixCard.className = 'bkbg-cm-card'; |
| 215 |
mixCard.style.background = a.cardBg; |
| 216 |
wrap.appendChild(mixCard); |
| 217 |
|
| 218 |
var ratioRow = document.createElement('div'); |
| 219 |
ratioRow.className = 'bkbg-cm-ratio-row'; |
| 220 |
var ratioLabelA = document.createElement('span'); |
| 221 |
ratioLabelA.style.color = a.labelColor; |
| 222 |
var ratioTitle = document.createElement('span'); |
| 223 |
ratioTitle.style.cssText = 'font-weight:600;color:' + a.labelColor + ';'; |
| 224 |
ratioTitle.textContent = 'Mix Ratio'; |
| 225 |
var ratioLabelB = document.createElement('span'); |
| 226 |
ratioLabelB.style.color = a.labelColor; |
| 227 |
[ratioLabelA, ratioTitle, ratioLabelB].forEach(function (el) { ratioRow.appendChild(el); }); |
| 228 |
mixCard.appendChild(ratioRow); |
| 229 |
|
| 230 |
var slider = document.createElement('input'); |
| 231 |
slider.type = 'range'; |
| 232 |
slider.min = 0; slider.max = 100; slider.value = Math.round(ratio * 100); |
| 233 |
slider.className = 'bkbg-cm-slider'; |
| 234 |
slider.addEventListener('input', function () { |
| 235 |
ratio = parseInt(slider.value) / 100; |
| 236 |
renderAll(); |
| 237 |
}); |
| 238 |
mixCard.appendChild(slider); |
| 239 |
|
| 240 |
var resultSwatch = document.createElement('div'); |
| 241 |
resultSwatch.className = 'bkbg-cm-result-swatch'; |
| 242 |
mixCard.appendChild(resultSwatch); |
| 243 |
|
| 244 |
/* Formats */ |
| 245 |
var formatsEl = null, copiedHint = null; |
| 246 |
if (a.showFormats) { |
| 247 |
formatsEl = document.createElement('div'); |
| 248 |
formatsEl.className = 'bkbg-cm-formats'; |
| 249 |
mixCard.appendChild(formatsEl); |
| 250 |
|
| 251 |
copiedHint = document.createElement('div'); |
| 252 |
copiedHint.className = 'bkbg-cm-copied-hint'; |
| 253 |
copiedHint.style.color = a.accentColor; |
| 254 |
copiedHint.textContent = 'Copied!'; |
| 255 |
mixCard.appendChild(copiedHint); |
| 256 |
} |
| 257 |
|
| 258 |
/* Gradient */ |
| 259 |
var gradientEl = null; |
| 260 |
if (a.showGradient) { |
| 261 |
var gradCard = document.createElement('div'); |
| 262 |
gradCard.className = 'bkbg-cm-card'; |
| 263 |
gradCard.style.background = a.cardBg; |
| 264 |
var gradTitle = document.createElement('div'); |
| 265 |
gradTitle.style.cssText = 'font-size:13px;font-weight:600;color:' + a.labelColor + ';margin-bottom:8px;'; |
| 266 |
gradTitle.textContent = 'Gradient (A → B)'; |
| 267 |
gradCard.appendChild(gradTitle); |
| 268 |
gradientEl = document.createElement('div'); |
| 269 |
gradientEl.className = 'bkbg-cm-gradient-bar'; |
| 270 |
gradCard.appendChild(gradientEl); |
| 271 |
wrap.appendChild(gradCard); |
| 272 |
} |
| 273 |
|
| 274 |
/* Harmonies */ |
| 275 |
var harmoniesEl = null; |
| 276 |
if (a.showHarmonies) { |
| 277 |
var harmCard = document.createElement('div'); |
| 278 |
harmCard.className = 'bkbg-cm-card'; |
| 279 |
harmCard.style.background = a.cardBg; |
| 280 |
var harmTitle = document.createElement('div'); |
| 281 |
harmTitle.className = 'bkbg-cm-harmonies-title'; |
| 282 |
harmTitle.style.color = a.labelColor; |
| 283 |
harmTitle.textContent = 'Color Harmonies (of mixed color)'; |
| 284 |
harmCard.appendChild(harmTitle); |
| 285 |
harmoniesEl = document.createElement('div'); |
| 286 |
harmCard.appendChild(harmoniesEl); |
| 287 |
wrap.appendChild(harmCard); |
| 288 |
} |
| 289 |
|
| 290 |
function makeFormatBox(val, lbl) { |
| 291 |
var box = document.createElement('div'); |
| 292 |
box.className = 'bkbg-cm-format-box'; |
| 293 |
box.title = 'Click to copy'; |
| 294 |
var valEl = document.createElement('div'); |
| 295 |
valEl.className = 'bkbg-cm-format-val'; |
| 296 |
valEl.style.color = a.labelColor; |
| 297 |
valEl.textContent = val; |
| 298 |
var lblEl = document.createElement('div'); |
| 299 |
lblEl.className = 'bkbg-cm-format-lbl'; |
| 300 |
lblEl.textContent = lbl; |
| 301 |
box.appendChild(valEl); |
| 302 |
box.appendChild(lblEl); |
| 303 |
box.addEventListener('click', function () { |
| 304 |
navigator.clipboard && navigator.clipboard.writeText(val).catch(function () { |
| 305 |
var t = document.createElement('textarea'); t.value = val; |
| 306 |
document.body.appendChild(t); t.select(); document.execCommand('copy'); document.body.removeChild(t); |
| 307 |
}); |
| 308 |
var orig = valEl.textContent; |
| 309 |
valEl.textContent = 'Copied!'; |
| 310 |
if (copiedHint) { copiedHint.classList.add('bkbg-cm-show'); } |
| 311 |
setTimeout(function () { |
| 312 |
valEl.textContent = orig; |
| 313 |
if (copiedHint) copiedHint.classList.remove('bkbg-cm-show'); |
| 314 |
}, 1500); |
| 315 |
}); |
| 316 |
return box; |
| 317 |
} |
| 318 |
|
| 319 |
function makeHarmonyGroup(title, colors) { |
| 320 |
var group = document.createElement('div'); |
| 321 |
group.className = 'bkbg-cm-harmony-group'; |
| 322 |
var lbl = document.createElement('div'); |
| 323 |
lbl.className = 'bkbg-cm-harmony-label'; |
| 324 |
lbl.style.color = a.labelColor; |
| 325 |
lbl.textContent = title; |
| 326 |
group.appendChild(lbl); |
| 327 |
var swatches = document.createElement('div'); |
| 328 |
swatches.className = 'bkbg-cm-harmony-swatches'; |
| 329 |
colors.forEach(function (c) { |
| 330 |
var col = document.createElement('div'); |
| 331 |
var sw = document.createElement('button'); |
| 332 |
sw.className = 'bkbg-cm-harmony-swatch'; |
| 333 |
sw.style.background = c; |
| 334 |
sw.title = c.toUpperCase() + ' – click to copy'; |
| 335 |
sw.addEventListener('click', function () { |
| 336 |
navigator.clipboard && navigator.clipboard.writeText(c.toUpperCase()).catch(function(){}); |
| 337 |
var orig = sw.style.transform; sw.style.transform = 'scale(0.9)'; |
| 338 |
setTimeout(function(){sw.style.transform = orig;},200); |
| 339 |
}); |
| 340 |
var hx = document.createElement('div'); |
| 341 |
hx.className = 'bkbg-cm-harmony-hex'; |
| 342 |
hx.style.color = a.labelColor; |
| 343 |
hx.textContent = c.toUpperCase(); |
| 344 |
col.appendChild(sw); |
| 345 |
col.appendChild(hx); |
| 346 |
swatches.appendChild(col); |
| 347 |
}); |
| 348 |
group.appendChild(swatches); |
| 349 |
return group; |
| 350 |
} |
| 351 |
|
| 352 |
function renderAll() { |
| 353 |
var t = ratio; |
| 354 |
var mixed = mixColors(colorA, colorB, t); |
| 355 |
var aStr = Math.round((1-t)*100); |
| 356 |
var bStr = Math.round(t*100); |
| 357 |
|
| 358 |
ratioLabelA.textContent = 'A (' + aStr + '%)'; |
| 359 |
ratioLabelB.textContent = 'B (' + bStr + '%)'; |
| 360 |
|
| 361 |
// slider track gradient |
| 362 |
slider.style.background = 'linear-gradient(to right, ' + colorA + ' 0%, ' + mixed + ' ' + (t*100) + '%, ' + colorB + ' 100%)'; |
| 363 |
slider.style.color = mixed; |
| 364 |
|
| 365 |
resultSwatch.style.background = mixed; |
| 366 |
resultSwatch.title = mixed.toUpperCase(); |
| 367 |
|
| 368 |
if (formatsEl) { |
| 369 |
formatsEl.innerHTML = ''; |
| 370 |
[ |
| 371 |
{ val: mixed.toUpperCase(), lbl: 'HEX' }, |
| 372 |
{ val: formatRgb(mixed), lbl: 'RGB' }, |
| 373 |
{ val: formatHsl(mixed), lbl: 'HSL' } |
| 374 |
].forEach(function (f) { formatsEl.appendChild(makeFormatBox(f.val, f.lbl)); }); |
| 375 |
} |
| 376 |
|
| 377 |
if (gradientEl) { |
| 378 |
gradientEl.style.background = 'linear-gradient(to right, ' + colorA + ', ' + colorB + ')'; |
| 379 |
} |
| 380 |
|
| 381 |
if (harmoniesEl) { |
| 382 |
harmoniesEl.innerHTML = ''; |
| 383 |
var h = getHarmonies(mixed); |
| 384 |
[ |
| 385 |
{ title: 'Complementary', colors: h.complementary }, |
| 386 |
{ title: 'Analogous', colors: h.analogous }, |
| 387 |
{ title: 'Triadic', colors: h.triadic }, |
| 388 |
{ title: 'Split-Comp.', colors: h.split } |
| 389 |
].forEach(function (g) { harmoniesEl.appendChild(makeHarmonyGroup(g.title, g.colors)); }); |
| 390 |
} |
| 391 |
} |
| 392 |
|
| 393 |
renderAll(); |
| 394 |
} |
| 395 |
|
| 396 |
function init() { |
| 397 |
document.querySelectorAll('.bkbg-cm-app').forEach(initBlock); |
| 398 |
} |
| 399 |
|
| 400 |
if (document.readyState === 'loading') { |
| 401 |
document.addEventListener('DOMContentLoaded', init); |
| 402 |
} else { |
| 403 |
init(); |
| 404 |
} |
| 405 |
})(); |
| 406 |
|