Skip to main content

Gradient Generation Algorithm

This guide explains the gradient generation algorithm used in the Retro Floppy component to create beautiful, harmonious color gradients for disk labels.

Overview​

The gradient generation algorithm creates soft, pastel-colored gradients that are:

  • Deterministic: Same label name always produces the same gradient
  • Harmonious: Colors work well together (no clashing)
  • Accessible: High contrast with text for readability
  • Unique: Different labels get different gradients

Algorithm Steps​

1. Generate Seed from Label Name​

Convert the label name to a numeric seed using a hash function:

function stringToSeed(str: string): number {
if (!str) return 12345; // Default seed

let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // Convert to 32-bit integer
}

return Math.abs(hash);
}

Example:

  • "My Disk" → 1234567890
  • "Another Disk" → 9876543210

2. Create Seeded Random Number Generator​

Use the Mulberry32 algorithm for deterministic randomness:

function createSeededRandom(seed: number): () => number {
let state = seed;
return () => {
state = (state + 0x6d2b79f5) | 0;
let t = Math.imul(state ^ (state >>> 15), 1 | state);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}

Why Mulberry32?

  • Fast (~10x faster than Math.random())
  • Deterministic (same seed = same sequence)
  • Good distribution (passes statistical tests)
  • Small code size (~6 lines)

3. Generate Color Palette​

Create 2-3 harmonious colors using the analogous color scheme:

function generateColorPalette(seed: number): string[] {
const random = createSeededRandom(seed);

// 1. Choose base hue (0-360 degrees on color wheel)
const baseHue = random() * 360;

// 2. Decide how many colors (2-3)
const colorCount = 2 + Math.floor(random() * 2);

const colors: string[] = [];
for (let i = 0; i < colorCount; i++) {
// 3. Create analogous colors (15-45 degree shifts)
const hueOffset = (random() * 30 + 15) * i;
const hue = (baseHue + hueOffset) % 360;

// 4. Use pastel saturation (25-50%)
const saturation = 25 + random() * 25;

// 5. Use high lightness (65-85%)
const lightness = 65 + random() * 20;

colors.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`);
}

return colors;
}

4. Generate CSS Gradient​

Convert colors to CSS gradient string:

function generateGradientCSS(
colors: string[],
type: 'linear' | 'radial' | 'conic',
random: () => number,
): string {
if (type === 'linear') {
const angle = Math.round(random() * 360);
return `linear-gradient(${angle}deg, ${colors.join(', ')})`;
}
// ... radial and conic implementations
}

5. Calculate Adaptive Text Color​

Determine whether to use black or white text for optimal contrast:

function getAdaptiveTextColor(colors: string[]): string {
// 1. Calculate average luminance of gradient
const gradientLuminance = calculateGradientLuminance(colors);

// 2. Calculate contrast with white and black
const contrastWithWhite = calculateContrastRatio(gradientLuminance, 1);
const contrastWithBlack = calculateContrastRatio(gradientLuminance, 0);

// 3. Return color with better contrast
return contrastWithWhite > contrastWithBlack ? '#ffffff' : '#000000';
}

Color Theory Principles​

HSL Color Space​

We use HSL (Hue, Saturation, Lightness) instead of RGB:

  • Hue (0-360°): Position on color wheel
    • 0° = Red
    • 120° = Green
    • 240° = Blue
  • Saturation (0-100%): Color intensity
    • 0% = Gray (no color)
    • 100% = Vivid (full color)
  • Lightness (0-100%): Brightness
    • 0% = Black
    • 50% = Pure color
    • 100% = White

Why HSL?

  • Intuitive color manipulation
  • Easy to create harmonious palettes
  • Simple pastel generation (low saturation + high lightness)

Analogous Color Scheme​

Analogous colors are adjacent on the color wheel (within 30-60 degrees):

Base Hue: 210° (Blue)
Color 1: 210° (Blue)
Color 2: 225° (Blue-Purple) - 15° shift
Color 3: 240° (Purple) - 30° shift

Why Analogous?

  • Creates harmonious, non-clashing gradients
  • Feels cohesive and professional
  • Avoids jarring color combinations

Alternatives (Not Used):

  • Complementary (180° apart): Too high contrast, clashing
  • Triadic (120° apart): Too varied, chaotic
  • Monochromatic (same hue): Too boring, lacks interest

Pastel Optimization​

Pastel colors are achieved through:

  1. Low Saturation (25-50%)

    • Muted, soft colors
    • Less intense than vivid colors
    • Easier on the eyes
  2. High Lightness (65-85%)

    • Bright, airy appearance
    • Good contrast with dark text
    • Retro aesthetic

Example:

/* Vivid Blue */
hsl(210, 100%, 50%) /* Bright, intense */

/* Pastel Blue */
hsl(210, 35%, 75%) /* Soft, muted */

Deterministic Generation​

Why Deterministic?​

Same label name always produces the same gradient:

Benefits:

  • Consistent appearance across sessions
  • Predictable for users
  • No need to store gradient data
  • Shareable (screenshots match live view)

Example:

generateLabelGradient('My Disk', 'linear');
// Always produces: linear-gradient(135deg, hsl(210, 35%, 75%), ...)

generateLabelGradient('My Disk', 'linear');
// Same result every time

Custom Override​

Users can override with custom seed or colors:

// Custom seed for different gradient
generateLabelGradient('My Disk', 'linear', { seed: 42 });

// Custom colors
generateLabelGradient('My Disk', 'linear', {
colors: ['#ff6b6b', '#4ecdc4'],
});

Gradient Types​

Linear Gradient​

linear-gradient(135deg, hsl(210, 35%, 75%) 0%, hsl(225, 40%, 70%) 100%)
  • Angle: 0-360 degrees (random)
  • Color stops: Evenly distributed
  • Most common type

Radial Gradient​

radial-gradient(circle at 50% 50%, hsl(210, 35%, 75%) 0%, ...)
  • Center position: 30-70% (random)
  • Shape: Circle
  • Creates focal point effect

Conic Gradient​

conic-gradient(from 45deg, hsl(210, 35%, 75%) 0deg, ...)
  • Starting angle: 0-360 degrees (random)
  • Color stops: Evenly distributed around circle
  • Creates rainbow/pie effect

WCAG Contrast Compliance​

Luminance Calculation​

Convert HSL → RGB → Relative Luminance:

// 1. Convert HSL to RGB
const rgb = hslToRgb('hsl(210, 35%, 75%)');
// { r: 166, g: 191, b: 217 }

// 2. Calculate relative luminance (WCAG formula)
const luminance = calculateLuminance(rgb.r, rgb.g, rgb.b);
// 0.45 (0 = black, 1 = white)

// 3. Calculate contrast ratio
const contrast = calculateContrastRatio(0.45, 0); // vs black
// 9.1:1 (exceeds WCAG AA requirement of 4.5:1)

Text Color Selection​

// Gradient luminance: 0.45 (medium-light)
// Contrast with white: 2.2:1 (insufficient)
// Contrast with black: 9.1:1 (excellent)
// Result: Use black text

WCAG Requirements:

  • AA Normal Text: 4.5:1 minimum
  • AA Large Text: 3:1 minimum
  • AAA Normal Text: 7:1 minimum

Our pastel gradients (65-85% lightness) typically achieve 7-15:1 contrast with black text.

Performance​

Generation Cost​

stringToSeed():           ~0.01ms
createSeededRandom(): ~0.001ms
generateColorPalette(): ~0.05ms
generateGradientCSS(): ~0.01ms
getAdaptiveTextColor(): ~0.1ms
Total: ~0.17ms

Optimization: Results are memoized with useMemo:

const gradientConfig = useMemo(() => {
return generateLabelGradient(label.name, gradientType);
}, [label.name, gradientType]);

Only regenerates when label name or type changes.

See Also​