PageSourceSearch

https://shop.naturasugars.co.za/assets/js/voltcor/colour_changer.js

js naturasugars.co.za collected 2026-09-27 01:06:59 UTC 10,417 bytes, 365 lines download raw bytes

1function rgbToHex(r, g, b) {
2    function componentToHex(c) {
3      var hex = c.toString(16);
4      return hex.length == 1 ? "0" + hex : hex;
5    }
6  
7    return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
8  }
9  
10  function hexToRgb(hex) {
11    // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
12    const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
13    hex = hex.replace(shorthandRegex, (m, r, g, b) => {
14      return r + r + g + g + b + b;
15    });
16  
17    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
18    return result
19      ? [
20          parseInt(result[1], 16),
21          parseInt(result[2], 16),
22          parseInt(result[3], 16),
23        ]
24      : null;
25  }
26  
27  class Color {
28    constructor(r, g, b) {
29      this.set(r, g, b);
30    }
31  
32    toRgb() {
33      return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)})`;
34    }
35  
36    toHex() {
37      return rgbToHex(Math.round(this.r), Math.round(this.g), Math.round(this.b));
38    }
39  
40    set(r, g, b) {
41      this.r = this.clamp(r);
42      this.g = this.clamp(g);
43      this.b = this.clamp(b);
44    }
45  
46    hueRotate(angle = 0) {
47      angle = angle / 180 * Math.PI;
48      const sin = Math.sin(angle);
49      const cos = Math.cos(angle);
50  
51      this.multiply([
52        0.213 + cos * 0.787 - sin * 0.213,
53        0.715 - cos * 0.715 - sin * 0.715,
54        0.072 - cos * 0.072 + sin * 0.928,
55        0.213 - cos * 0.213 + sin * 0.143,
56        0.715 + cos * 0.285 + sin * 0.140,
57        0.072 - cos * 0.072 - sin * 0.283,
58        0.213 - cos * 0.213 - sin * 0.787,
59        0.715 - cos * 0.715 + sin * 0.715,
60        0.072 + cos * 0.928 + sin * 0.072,
61      ]);
62    }
63  
64    grayscale(value = 1) {
65      this.multiply([
66        0.2126 + 0.7874 * (1 - value),
67        0.7152 - 0.7152 * (1 - value),
68        0.0722 - 0.0722 * (1 - value),
69        0.2126 - 0.2126 * (1 - value),
70        0.7152 + 0.2848 * (1 - value),
71        0.0722 - 0.0722 * (1 - value),
72        0.2126 - 0.2126 * (1 - value),
73        0.7152 - 0.7152 * (1 - value),
74        0.0722 + 0.9278 * (1 - value),
75      ]);
76    }
77  
78    sepia(value = 1) {
79      this.multiply([
80        0.393 + 0.607 * (1 - value),
81        0.769 - 0.769 * (1 - value),
82        0.189 - 0.189 * (1 - value),
83        0.349 - 0.349 * (1 - value),
84        0.686 + 0.314 * (1 - value),
85        0.168 - 0.168 * (1 - value),
86        0.272 - 0.272 * (1 - value),
87        0.534 - 0.534 * (1 - value),
88        0.131 + 0.869 * (1 - value),
89      ]);
90    }
91  
92    saturate(value = 1) {
93      this.multiply([
94        0.213 + 0.787 * value,
95        0.715 - 0.715 * value,
96        0.072 - 0.072 * value,
97        0.213 - 0.213 * value,
98        0.715 + 0.285 * value,
99        0.072 - 0.072 * value,
100        0.213 - 0.213 * value,
101        0.715 - 0.715 * value,
102        0.072 + 0.928 * value,
103      ]);
104    }
105  
106    multiply(matrix) {
107      const newR = this.clamp(this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2]);
108      const newG = this.clamp(this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5]);
109      const newB = this.clamp(this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8]);
110      this.r = newR;
111      this.g = newG;
112      this.b = newB;
113    }
114  
115    brightness(value = 1) {
116      this.linear(value);
117    }
118    contrast(value = 1) {
119      this.linear(value, -(0.5 * value) + 0.5);
120    }
121  
122    linear(slope = 1, intercept = 0) {
123      this.r = this.clamp(this.r * slope + intercept * 255);
124      this.g = this.clamp(this.g * slope + intercept * 255);
125      this.b = this.clamp(this.b * slope + intercept * 255);
126    }
127  
128    invert(value = 1) {
129      this.r = this.clamp((value + this.r / 255 * (1 - 2 * value)) * 255);
130      this.g = this.clamp((value + this.g / 255 * (1 - 2 * value)) * 255);
131      this.b = this.clamp((value + this.b / 255 * (1 - 2 * value)) * 255);
132    }
133  
134    hsl() {
135      // Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA.
136      const r = this.r / 255;
137      const g = this.g / 255;
138      const b = this.b / 255;
139      const max = Math.max(r, g, b);
140      const min = Math.min(r, g, b);
141      let h, s, l = (max + min) / 2;
142  
143      if (max === min) {
144        h = s = 0;
145      } else {
146        const d = max - min;
147        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
148        switch (max) {
149          case r:
150            h = (g - b) / d + (g < b ? 6 : 0);
151            break;
152  
153          case g:
154            h = (b - r) / d + 2;
155            break;
156  
157          case b:
158            h = (r - g) / d + 4;
159            break;
160        }
161        h /= 6;
162      }
163  
164      return {
165        h: h * 100,
166        s: s * 100,
167        l: l * 100,
168      };
169    }
170  
171    clamp(value) {
172      if (value > 255) {
173        value = 255;
174      } else if (value < 0) {
175        value = 0;
176      }
177      return value;
178    }
179  }
180  
181  class Solver {
182    constructor(target, baseColor) {
183      this.target = target;
184      this.targetHSL = target.hsl();
185      this.reusedColor = new Color(0, 0, 0);
186    }
187  
188    solve() {
189      const result = this.solveNarrow(this.solveWide());
190      return {
191        values: result.values,
192        loss: result.loss,
193        filter: this.css(result.values),
194        filterRaw: this.raw(result.values),
195      };
196    }
197  
198    solveWide() {
199      const A = 5;
200      const c = 15;
201      const a = [60, 180, 18000, 600, 1.2, 1.2];
202  
203      let best = { loss: Infinity };
204      for (let i = 0; best.loss > 25 && i < 3; i++) {
205        const initial = [50, 20, 3750, 50, 100, 100];
206        const result = this.spsa(A, a, c, initial, 1000);
207        if (result.loss < best.loss) {
208          best = result;
209        }
210      }
211      return best;
212    }
213  
214    solveNarrow(wide) {
215      const A = wide.loss;
216      const c = 2;
217      const A1 = A + 1;
218      const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1];
219      return this.spsa(A, a, c, wide.values, 500);
220    }
221  
222    spsa(A, a, c, values, iters) {
223      const alpha = 1;
224      const gamma = 0.16666666666666666;
225  
226      let best = null;
227      let bestLoss = Infinity;
228      const deltas = new Array(6);
229      const highArgs = new Array(6);
230      const lowArgs = new Array(6);
231  
232      for (let k = 0; k < iters; k++) {
233        const ck = c / Math.pow(k + 1, gamma);
234        for (let i = 0; i < 6; i++) {
235          deltas[i] = Math.random() > 0.5 ? 1 : -1;
236          highArgs[i] = values[i] + ck * deltas[i];
237          lowArgs[i] = values[i] - ck * deltas[i];
238        }
239  
240        const lossDiff = this.loss(highArgs) - this.loss(lowArgs);
241        for (let i = 0; i < 6; i++) {
242          const g = lossDiff / (2 * ck) * deltas[i];
243          const ak = a[i] / Math.pow(A + k + 1, alpha);
244          values[i] = fix(values[i] - ak * g, i);
245        }
246  
247        const loss = this.loss(values);
248        if (loss < bestLoss) {
249          best = values.slice(0);
250          bestLoss = loss;
251        }
252      }
253      return { values: best, loss: bestLoss };
254  
255      function fix(value, idx) {
256        let max = 100;
257        if (idx === 2 /* saturate */) {
258          max = 7500;
259        } else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) {
260          max = 200;
261        }
262  
263        if (idx === 3 /* hue-rotate */) {
264          if (value > max) {
265            value %= max;
266          } else if (value < 0) {
267            value = max + value % max;
268          }
269        } else if (value < 0) {
270          value = 0;
271        } else if (value > max) {
272          value = max;
273        }
274        return value;
275      }
276    }
277  
278    loss(filters) {
279      // Argument is array of percentages.
280      const color = this.reusedColor;
281      color.set(0, 0, 0);
282  
283      color.invert(filters[0] / 100);
284      color.sepia(filters[1] / 100);
285      color.saturate(filters[2] / 100);
286      color.hueRotate(filters[3] * 3.6);
287      color.brightness(filters[4] / 100);
288      color.contrast(filters[5] / 100);
289  
290      const colorHSL = color.hsl();
291      return (
292        Math.abs(color.r - this.target.r) +
293        Math.abs(color.g - this.target.g) +
294        Math.abs(color.b - this.target.b) +
295        Math.abs(colorHSL.h - this.targetHSL.h) +
296        Math.abs(colorHSL.s - this.targetHSL.s) +
297        Math.abs(colorHSL.l - this.targetHSL.l)
298      );
299    }
300  
301    raw(filters) {
302      function fmt(idx, multiplier = 1) {
303        return Math.round(filters[idx] * multiplier);
304      }
305      return `brightness(0) saturate(100%) invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(2)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(5)}%)`;
306    }
307  
308    css(filters) {
309      function fmt(idx, multiplier = 1) {
310        return Math.round(filters[idx] * multiplier);
311      }
312      return `filter: brightness(0) saturate(100%) invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(2)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(5)}%);`;
313    }
314  }
315  
316  function computeColourFilter(rgb) {
317  
318    if (rgb.length !== 3) {
319      alert('Invalid format!');
320      return;
321    }
322  
323    const color = new Color(rgb[0], rgb[1], rgb[2]);
324    const solver =  new Solver(color);
325    const result = solver.solve();
326    let lossMsg = '';
327    const res = {
328      color,
329      solver,
330      result,
331      lossMsg,
332    };
333
334    return result;
335  }
336  
337  function validateColor(color) {
338    const submitButton = document.getElementById('action-button');
339    const HEXColorRegExp = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
340    const isValid = HEXColorRegExp.test(color);
341  
342    if (isValid) {
343      submitButton.classList.remove('disabled');
344    } else if (!submitButton.classList.contains('disabled')) {
345      submitButton.classList.add('disabled');
346    }
347  }
348
349  function coloursOnStart(){
350    let rgb = $('.mobileOpen .links a, .mobileSubnav .links a ').css('color');
351    rgb = rgb.replace('rgb(', "").replace(')', '');
352    rgb = rgb.split(', ');
353    result = computeColourFilter(rgb).filter.replace(';', ' !important;');
354    
355    $('head').append('<style>.mobileNavigation .content img {' + result + '}');
356
357    let rgb_links = $('.navigation .linksContain a').css('color');
358    rgb_links = rgb_links.replace('rgb(', "").replace(')', '');
359    rgb_links = rgb_links.split(', ');
360    result = computeColourFilter(rgb_links).filter.replace(';', ' !important;');
361
362    $('head').append('<style>.navigation .linksContain .dropdown-toggle::after{' + result + '}');
363  }
364
365  coloursOnStart();

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.