Skip to content

Instantly share code, notes, and snippets.

@Razboy20
Created December 26, 2025 04:36
Show Gist options
  • Select an option

  • Save Razboy20/28ea01027b31f8d332bd24130517b18b to your computer and use it in GitHub Desktop.

Select an option

Save Razboy20/28ea01027b31f8d332bd24130517b18b to your computer and use it in GitHub Desktop.
// note: this is extremely ugly patched together code from *years* ago
// that wasn't meant to see the light of day.
// But hey, I suppose it 'works'.
//
// Fun fact: this code was initially written for animating gradients for minecraft titles!
//
// https://www.youtube.com/watch?v=Cc2nkx77U24
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : null;
}
/**
* Interpolates between two hex colors based on a weight value.
* Interpolation is linear, a better and more pleasant way would be to interpolate in HSL or OKLAB/OKHSL colorspaces.
*/
function pickHex(color1, color2, weight) {
color1 = hexToRgb(color1);
color2 = hexToRgb(color2);
weight = 1 - weight;
var w1 = weight;
var w2 = 1 - w1;
var rgb = [Math.round(color1[0] * w1 + color2[0] * w2), Math.round(color1[1] * w1 + color2[1] * w2), Math.round(color1[2] * w1 + color2[2] * w2)];
return rgbToHex(rgb[0], rgb[1], rgb[2]);
}
const colors = new Set();
/**
* Given a gradient and a string,
* interpolates the string's characters with colors from the gradient.
*/
function fullTextGradation(gradient, text) {
const result = [];
for (const li in text) {
const l = text[li];
let position = Math.round((1 / (text.length - 1)) * parseInt(li) * 100000) / 100000;
let colorRange;
for (let index in gradient) {
index = parseInt(index);
const value = gradient[index];
if (index == gradient.length - 1 && position > value[0]) position = value[0];
if (position <= value[0]) {
if (index == 0) index++;
colorRange = [index - 1, index, (position - gradient[index - 1][0]) / (gradient[index][0] - gradient[index - 1][0])];
break;
}
}
const color = pickHex(gradient[colorRange[0]][1], gradient[colorRange[1]][1], colorRange[2]);
colors.add(color);
// add colored text using webvtt syntax to result
result.push(`<c.color${color.toUpperCase().slice(1)}>${l}</c>`);
}
return result.join("");
}
// why is the gradient repeated 3 times? Because I was lazy.
var rainbow = [
[-2, rgbToHex(255, 0, 0)],
[-1.9, rgbToHex(255, 154, 0)],
[-1.8, rgbToHex(208, 222, 33)],
[-1.7, rgbToHex(79, 220, 74)],
[-1.6, rgbToHex(63, 218, 216)],
[-1.5, rgbToHex(47, 201, 226)],
[-1.4, rgbToHex(28, 127, 231)],
[-1.3, rgbToHex(95, 21, 242)],
[-1.2, rgbToHex(186, 12, 248)],
[-1.1, rgbToHex(251, 7, 217)],
[-1, rgbToHex(255, 0, 0)],
[-0.9, rgbToHex(255, 154, 0)],
[-0.8, rgbToHex(208, 222, 33)],
[-0.7, rgbToHex(79, 220, 74)],
[-0.6, rgbToHex(63, 218, 216)],
[-0.5, rgbToHex(47, 201, 226)],
[-0.4, rgbToHex(28, 127, 231)],
[-0.3, rgbToHex(95, 21, 242)],
[-0.2, rgbToHex(186, 12, 248)],
[-0.1, rgbToHex(251, 7, 217)],
[0, rgbToHex(255, 0, 0)],
[0.1, rgbToHex(255, 154, 0)],
[0.2, rgbToHex(208, 222, 33)],
[0.3, rgbToHex(79, 220, 74)],
[0.4, rgbToHex(63, 218, 216)],
[0.5, rgbToHex(47, 201, 226)],
[0.6, rgbToHex(28, 127, 231)],
[0.7, rgbToHex(95, 21, 242)],
[0.8, rgbToHex(186, 12, 248)],
[0.9, rgbToHex(251, 7, 217)],
[1, rgbToHex(255, 0, 0)]
];
// ---
// slapped on code to output webvtt
let output = "";
for (let i = 0; i < 80; i++) {
// create time code for subtitle relative to 0 based on 'i' for webvtt captions
// create webvtt time code where time is in milliseconds, and padd the seconds with 2 0's
function generateTimecode(time) {
const timecode = `00:00:${Math.floor(time / 1000)
.toString()
.padStart(2, "0")}.${Math.floor(time % 1000)
.toString()
.padStart(3, "0")}`;
return timecode;
}
const text = fullTextGradation(
rainbow.map(g => [g[0] + (i / 79) * 2, g[1]]),
"Rainbows are amazing!"
);
output += `${generateTimecode((i * 1000) / 10)} --> ${generateTimecode(((i + 1) * 1000) / 10)}\n${text}\n\n`;
}
let output_header = "WEBVTT\nStyle:\n";
// add cue styles based on colors
for (let color of colors) {
color = color.toUpperCase().slice(1);
output_header += `::cue(c.color${color}) { color: #${color};\n}\n`;
}
output = output_header + "##\n\n" + output;
// write 'output' to 'subtitlegradients.vtt'
import { writeFileSync } from "fs";
writeFileSync("subtitlegradients.vtt", output);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment