1class App { 2 3 constructor() { 4 5 this.currentPalette = [] 6 this.currentPalette2 = [] 7 this.initialDarkest = "" 8 this.currentHSL = [0, 0, 0] 9 this.elements = { 10 palettes: document.querySelectorAll("#palette li"), 11 newPalette: document.querySelector("#new-palette"), 12 shufflePalette: document.querySelector("#shuffle-palette"), 13 resetPalette: document.querySelector("#reset-palette"), 14 sliders: {}, 15 labels: document.querySelectorAll("#adjustments strong em"), 16 exportColors: document.querySelectorAll("a[data-export-format]") 17 } 18 19 this.createSliders() 20 this.attachEvents() 21 this.pickPalette() 22 } 23 24 createSliders() { 25 this.elements.sliders.hue = noUiSlider.create(document.querySelector("#range-slider-hue"), { 26 start: 0, 27 step: 2, 28 connect: true, 29 range: { 30 min: -180, 31 max: 180 32 } 33 }) 34 35 this.elements.sliders.saturation = noUiSlider.create(document.querySelector("#range-slider-saturation"), { 36 start: 0, 37 step: 2, 38 connect: true, 39 range: { 40 min: -100, 41 max: 100 42 } 43 }) 44 45 this.elements.sliders.lightness = noUiSlider.create(document.querySelector("#range-slider-lightness"), { 46 start: 0, 47 step: 2, 48 connect: true, 49 range: { 50 min: -100, 51 max: 100 52 } 53 }) 54 } 55 56 attachEvents() { 57 this.elements.newPalette.addEventListener("click", (event) => { 58 event.preventDefault() 59 event.stopPropagation() 60 this.pickPalette() 61 }) 62 63 let self = this 64 for (let [index, paletteCont] of this.elements.palettes.entries()) { 65 paletteCont.addEventListener("click", function (event) { 66 event.preventDefault() 67 event.stopPropagation() 68 self.copyColor(this) 69 }) 70 71 // bring back the default text when pointe leaves the palette 72 paletteCont.addEventListener("pointerleave", function (event) { 73 this.classList.remove("copied") 74 }) 75 } 76 77 // shuffle the HSL sliders (within boundaries) for the currently selected palette 78 this.elements.shufflePalette.addEventListener("click", (event) => { 79 event.preventDefault() 80 event.stopPropagation() 81 this.shufflePalette() 82 }) 83 84 // reset HSL sliders 85 this.elements.resetPalette.addEventListener("click", (event) => { 86 event.preventDefault() 87 event.stopPropagation() 88 this.resetSliders() 89 }) 90 91 for (let [index, exportColor] of this.elements.exportColors.entries()) { 92 exportColor.addEventListener("click", function (event) { 93 event.preventDefault() 94 event.stopPropagation() 95 self.exportColors(this.dataset.exportFormat) 96 }) 97 } 98 99 let setHue = (values, handle) => { 100 let value = parseFloat(values.pop()) 101 this.currentHSL[0] = value 102 this.elements.labels[0].innerHTML = `${value}°` 103 this.setHSL() 104 } 105 106 let setSaturation = (values, handle) => { 107 let value = parseFloat(values.pop()) 108 this.currentHSL[1] = value / 100 109 this.elements.labels[1].innerHTML = `${value}%` 110 this.setHSL() 111 } 112 113 let setLightness = (values, handle) => { 114 let value = parseFloat(values.pop()) 115 this.currentHSL[2] = value / 100 116 this.elements.labels[2].innerHTML = `${value}%` 117 this.setHSL() 118 } 119 120 this.elements.sliders.hue.on("slide", setHue) 121 this.elements.sliders.hue.on("set", setHue) 122 123 this.elements.sliders.saturation.on("slide", setSaturation) 124 this.elements.sliders.saturation.on("set", setSaturation) 125 126 this.elements.sliders.lightness.on("slide", setLightness) 127 this.elements.sliders.lightness.on("set", setLightness) 128 } 129 130 copyColor(element) { 131 let hex = element.querySelector("h2").innerText 132 133 try { 134 navigator.clipboard.writeText(hex) 135 element.classList.add("copied") 136 } catch (err) { 137 console.error('Failed to copy: ', err) 138 } 139 } 140 141 setHSL() { 142 143 // clone the current palette so the original always stays intact 144 let contextPalette = structuredClone(this.currentPalette) 145 146 for (let [index, palette] of contextPalette.entries()) { 147 contextPalette[index].color = this.modifyHSL(contextPalette[index].color) 148 } 149 150 this.currentPalette2 = this.withLighter(contextPalette) 151 this.renderPalette(this.currentPalette2) 152 this.toggleReset() 153 } 154 155 modifyHSL(color) { 156 let hue = chroma(color).hsl()[0] 157 158 color = chroma(color) 159 .set("hsl.h", hue + this.currentHSL[0]) 160 .saturate(this.currentHSL[1]) 161 .brighten(this.currentHSL[2]) 162 .hex() 163 164 return color 165 } 166 167 toggleReset() { 168 this.elements.resetPalette.classList[this.currentHSL[0] == 0 && this.currentHSL[1] == 0 && this.currentHSL[2] == 0 ? "remove" : "add"]("show") 169 } 170 171 withLighter(bases) { 172 let lights = bases.map(item => ({ 173 color: chroma(item.color).brighten(0.8).hex(), 174 name: item.name + " Light" 175 })) 176
177 return bases.concat(lights) 178 } 179 180 pickPalette() { 181 182 // pick a random palette 183 this.currentPalette = window.hexes[Math.floor(Math.random() * window.hexes.length)] 184 185 this.pickDarkest() 186 this.currentPalette2 = this.withLighter(this.currentPalette) 187 this.renderPalette(this.currentPalette2) 188 this.resetSliders() 189 } 190 191 randomValue(min, max) { 192 return Math.floor(min + Math.random() * (max - min + 1)) 193 } 194 195 shufflePalette() { 196 this.elements.sliders.hue.set(this.randomValue(-30, 30)) 197 this.elements.sliders.saturation.set(this.randomValue(-60, 30)) 198 this.elements.sliders.lightness.set(this.randomValue(-30, 60)) 199 } 200 201 resetSliders() { 202 203 // reset the sliders to their default position 204 for (let [index, slider] of Object.entries(this.elements.sliders)) { 205 slider.reset() 206 } 207 208 // reset any adjusted HSL values 209 this.currentHSL = [0, 0, 0] 210 } 211 212 renderPalette(contextPalette) { 213 for (let [index, paletteCont] of this.elements.palettes.entries()) { 214 this.setInfo(paletteCont, contextPalette[index]) 215 } 216 217 // update the slider knob colors with the darkest (initially) color from the palette 218 this.updateUI(this.modifyHSL(this.initialDarkest)) 219 } 220 221 setInfo(element, palette) { 222 element.style.backgroundColor = palette.color 223 224 element.innerHTML = ` 225 <h2>${palette.color}</h2> 226 <h3>${palette.name}</h3> 227 ` 228 229 // add class if single palette color is dark for an alternative text color 230 element.classList[chroma(palette.color).luminance() > 0.4 ? "remove" : "add"]("lod") 231 } 232 233 darkestColor(colors) { 234 235 // flatten the array of objects to an array of colors 236 colors = colors.map(item => item.color) 237 238 // convert the colors to luminance values 239 let inputArray = colors.map(color => chroma(color).luminance()) 240 241 // find the index of the darkest color 242 const minNumber = Math.min(...inputArray) 243 244 // return the color with the darkest luminance 245 return colors[inputArray.indexOf(minNumber)] 246 } 247 248 pickDarkest() { 249 250 // pick the darkest color from the palette unless the first one isn't 251 // actually that light in the luminance estimation. This is because the UI looks better 252 // if the accent color is the same as the first color in the palette 253 if (chroma(this.currentPalette[0].color).luminance() > 0.6) { 254 this.initialDarkest = this.darkestColor(this.currentPalette) 255 } else { 256 this.initialDarkest = this.currentPalette[0].color 257 } 258 } 259 260 updateUI(color) { 261 let cssRoot = document.querySelector(":root") 262 cssRoot.style.setProperty("--ui-accent", color) 263 } 264 265 exportColors(format) { 266 let mode = format === "hex" ? "hex" : "css" 267 let templateColors = [] 268 let template = "" 269 270 for (let [index, paletteCont] of this.currentPalette2.entries()) { 271 templateColors.push(` --color-${index + 1}: ${chroma(paletteCont.color)[mode](format)};`) 272 } 273 274 template = `:root {\n${templateColors.join("\n")}\n}\n\n` 275 276 document.getElementById('css-output').value = template; 277 278 // push color payload to the parent frame so it can handle the injection 279 /* 280 window.parent.postMessage({ 281 type: "export-colors", 282 payload: template 283 }, "*") 284 /**/ 285 } 286} 287 288window.addEventListener("DOMContentLoaded", () => { 289 new App() 290})
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.