diff --git a/site/gradient-bands/index.html b/site/gradient-bands/index.html
new file mode 100644
index 0000000..70b9471
--- /dev/null
+++ b/site/gradient-bands/index.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+ Gradient Bands - Tools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/site/gradient-bands/main.js b/site/gradient-bands/main.js
new file mode 100644
index 0000000..d2fa76d
--- /dev/null
+++ b/site/gradient-bands/main.js
@@ -0,0 +1,99 @@
+function hexToRgb(hex) {
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return result ? {
+ r: parseInt(result[1], 16),
+ g: parseInt(result[2], 16),
+ b: parseInt(result[3], 16)
+ } : null;
+}
+
+function interpolateColor(startRgb, endRgb, t) {
+ return {
+ r: Math.round(startRgb.r + (endRgb.r - startRgb.r) * t),
+ g: Math.round(startRgb.g + (endRgb.g - startRgb.g) * t),
+ b: Math.round(startRgb.b + (endRgb.b - startRgb.b) * t)
+ };
+}
+
+function rgbToString(rgb) {
+ return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
+}
+
+function getBandColorAtT(startRgb, endRgb, bands, t) {
+ const bandIndex = Math.floor(t * bands);
+ const bandT = bandIndex / (bands - 1);
+ const clampedBandT = Math.max(0, Math.min(1, bandT));
+ return interpolateColor(startRgb, endRgb, clampedBandT);
+}
+
+function applyFunction(t, funcType) {
+ switch ( funcType) {
+ case 'linear':
+ return t;
+ case 'quad':
+ return t * t;
+ case 'cubic':
+ return t * t * t;
+ case 'sin':
+ return -Math.cos(t * Math.PI) / 2.0 + 0.5;
+ }
+}
+
+function renderGradient() {
+ const canvas = document.getElementById('gradient-canvas');
+ const ctx = canvas.getContext('2d');
+ const startColor = document.getElementById('start-color').value;
+ const endColor = document.getElementById('end-color').value;
+ const bands = parseInt(document.getElementById('bands').value);
+ const funcType = document.getElementById('func').value;
+
+ const startRgb = hexToRgb(startColor);
+ const endRgb = hexToRgb(endColor);
+
+ if (!startRgb || !endRgb || bands < 1) {
+ return;
+ }
+
+ const width = canvas.width;
+ const height = canvas.height;
+
+ const imageData = ctx.createImageData(width, height);
+ const data = imageData.data;
+
+ for (let y = 0; y < height; y++) {
+ const t = y / (height - 1);
+ const u = applyFunction(t, funcType);
+ const color = getBandColorAtT(startRgb, endRgb, bands, u);
+
+ for (let x = 0; x < width; x++) {
+ const index = (y * width + x) * 4;
+ data[index] = color.r;
+ data[index + 1] = color.g;
+ data[index + 2] = color.b;
+ data[index + 3] = 255;
+ }
+ }
+
+ ctx.putImageData(imageData, 0, 0);
+}
+
+function downloadCanvas() {
+ const canvas = document.getElementById('gradient-canvas');
+ const link = document.createElement('a');
+ link.download = 'gradient-bands.png';
+ link.href = canvas.toDataURL();
+ link.click();
+}
+
+function initializeEventListeners() {
+ document.getElementById('start-color').addEventListener('input', renderGradient);
+ document.getElementById('end-color').addEventListener('input', renderGradient);
+ document.getElementById('bands').addEventListener('input', renderGradient);
+ document.getElementById('func').addEventListener('input', renderGradient);
+ document.getElementById('download-btn').addEventListener('click', downloadCanvas);
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ initializeEventListeners();
+ renderGradient();
+});
\ No newline at end of file
diff --git a/site/gradient-bands/style.css b/site/gradient-bands/style.css
new file mode 100644
index 0000000..9d42d11
--- /dev/null
+++ b/site/gradient-bands/style.css
@@ -0,0 +1,4 @@
+canvas {
+ display: block;
+ margin-block-end: 12px;
+}
\ No newline at end of file
diff --git a/site/index.html b/site/index.html
index 3e33e42..f0756cc 100644
--- a/site/index.html
+++ b/site/index.html
@@ -22,6 +22,7 @@
Clocks
Freelens Logo Maker
Go Template Playground
+ Gradient Bands
Two-letter Country Codes