The Muddy Middle Problem
When interpolating between two vibrant colors in sRGB, the intermediate values often pass through a desaturated, grayish region. This is the “muddy middle” problem that makes sRGB gradients look unprofessional.Copy
┌───────────────────────────────────────────────────────────────────────────────┐
│ COLOR SPACE GEOMETRY COMPARISON │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ sRGB (Gamma-Corrected RGB) │
│ ========================== │
│ │
│ Structure: 3D Cube (R, G, B axes) │
│ │
│ G (Green) │
│ ^ │
│ │ ┌────────┐ │
│ │ /│ /│ │
│ │ / │ / │ │
│ │ ┌────────┐ │ │
│ │ │ │ │ │ │
│ │ │ └─────│──┘ ────→ R (Red) │
│ │ │ / │ / │
│ │ │/ │/ │
│ │ └────────┘ │
│ │ / │
│ │ / │
│ │/ │
│ └──────────────────────→ B (Blue) │
│ │
│ Problem: Straight line in RGB != Perceptually straight │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ OKLAB (Perceptually Uniform) │
│ ============================ │
│ │
│ Structure: Cylindrical (L, a, b axes) │
│ │
│ L (Lightness) │
│ ^ │
│ │ │
│ │ . . . │
│ │ . . │
│ │ . . │
│ │ . + . <── Neutral axis (a=0, b=0) │
│ │ . . │
│ │ . . │
│ │ . . . │
│ │ │ │
│ └─────────│────────→ a (green-red axis) │
│ / │
│ / │
│ / │
│ v b (blue-yellow axis) │
│ │
│ Advantage: Straight line in OKLAB = Perceptually straight │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
Blue to Yellow: The Classic Example
The blue-to-yellow gradient demonstrates sRGB’s fundamental flaw most clearly.Copy
┌───────────────────────────────────────────────────────────────────────────────┐
│ BLUE TO YELLOW INTERPOLATION │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ sRGB Interpolation Path (Through Gray Zone): │
│ ============================================= │
│ │
│ Lightness │
│ ^ │
│ │ │
│ 1.0 ┼ * Yellow (1.0, 1.0, 0.0) │
│ │ / │
│ │ / │
│ 0.7 ┼ * <── Muddy brown/gray │
│ │ / │
│ │ / │
│ 0.5 ┼ * <── DESATURATED ZONE │
│ │ / │
│ │ / │
│ 0.3 ┼ * <── Muddy gray │
│ │/ │
│ 0.0 * Blue (0.0, 0.0, 1.0) │
│ ┼─────┼─────┼─────┼─────┼─────┼────→ Chroma │
│ 0 0.1 0.2 0.3 0.4 0.5 │
│ │
│ Notice: Path curves INWARD toward neutral axis (low chroma) │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ OKLAB Interpolation Path (Direct): │
│ =================================== │
│ │
│ Lightness │
│ ^ │
│ │ │
│ 1.0 ┼ * Yellow │
│ │ /│ │
│ │ / │ │
│ 0.7 ┼ * │ <── Cyan-ish (clean) │
│ │ / │ │
│ │ / │ │
│ 0.5 ┼ * │ <── Vibrant green-cyan │
│ │ / │ │
│ │ / │ │
│ 0.3 ┼ * │ <── Clean transition │
│ │ / │ │
│ 0.0 ┼ * Blue │ │
│ ┼─────┼─────┼─────┼─────┼─────┼────→ Chroma │
│ 0 0.1 0.2 0.3 0.4 0.5 │
│ │
│ Notice: Path maintains CONSTANT CHROMA (saturation preserved) │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
Mathematical Definition
Linear interpolation (lerp) produces dramatically different results depending on color space.Copy
┌───────────────────────────────────────────────────────────────────────────────┐
│ LINEAR INTERPOLATION FORMULA │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ Generic Formula: │
│ ================ │
│ │
│ result = A * (1 - t) + B * t │
│ │
│ Where: │
│ - A = start color │
│ - B = end color │
│ - t = interpolation parameter [0, 1] │
│ - t=0 gives A, t=1 gives B, t=0.5 gives midpoint │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ In sRGB: │
│ ========= │
│ │
│ R_result = R_A * (1 - t) + R_B * t │
│ G_result = G_A * (1 - t) + G_B * t │
│ B_result = B_A * (1 - t) + B_B * t │
│ │
│ Example: Blue (0, 0, 255) to Yellow (255, 255, 0) at t=0.5 │
│ │
│ R = 0 * 0.5 + 255 * 0.5 = 127.5 │
│ G = 0 * 0.5 + 255 * 0.5 = 127.5 │
│ B = 255 * 0.5 + 0 * 0.5 = 127.5 │
│ │
│ Result: rgb(127.5, 127.5, 127.5) = GRAY! │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ In OKLAB: │
│ ========== │
│ │
│ L_result = L_A * (1 - t) + L_B * t │
│ a_result = a_A * (1 - t) + a_B * t │
│ b_result = b_A * (1 - t) + b_B * t │
│ │
│ Blue in OKLAB: L=0.45, a=-0.03, b=-0.31 │
│ Yellow in OKLAB: L=0.97, a=-0.07, b=0.20 │
│ │
│ At t=0.5: │
│ L = 0.45 * 0.5 + 0.97 * 0.5 = 0.71 │
│ a = -0.03 * 0.5 + -0.07 * 0.5 = -0.05 │
│ b = -0.31 * 0.5 + 0.20 * 0.5 = -0.055 │
│ │
│ Result: A vibrant cyan-ish color (NOT gray!) │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
OKLAB vs OKLCH: Cartesian vs Polar
OKLAB and OKLCH represent the same perceptually uniform color space in different coordinate systems.Copy
┌───────────────────────────────────────────────────────────────────────────────┐
│ OKLAB vs OKLCH COORDINATE SYSTEMS │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ OKLAB (Cartesian): │
│ ================== │
│ │
│ Coordinates: L (Lightness), a (green-red), b (blue-yellow) │
│ │
│ a+ │
│ │ │
│ │ P(a, b) │
│ │ / │
│ │ / │
│ │ / │
│ │ / │
│ b─ ────────┼┼──────── b+ │
│ │ │
│ │ │
│ a─ │
│ │
│ Best for: INTERPOLATION (straight line = perceptual straight line) │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ OKLCH (Polar/Cylindrical): │
│ ========================== │
│ │
│ Coordinates: L (Lightness), C (Chroma), H (Hue angle) │
│ │
│ 0deg (Red) │
│ │ │
│ │ P(C, H) │
│ │ / │
│ │ / C (radius) │
│ │ / │
│ │ / H (angle) │
│ 270deg ────────┼──────── 90deg │
│ (Blue) │ (Yellow) │
│ │ │
│ 180deg (Green) │
│ │
│ Best for: DEFINING colors (human-intuitive: hue, saturation, brightness) │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ Conversion Between: │
│ =================== │
│ │
│ OKLAB to OKLCH: │
│ C = sqrt(a^2 + b^2) │
│ H = atan2(b, a) │
│ │
│ OKLCH to OKLAB: │
│ a = C * cos(H) │
│ b = C * sin(H) │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
Side-by-Side Comparison
Copy
┌───────────────────────────────────────────────────────────────────────────────┐
│ sRGB vs OKLAB: SIDE-BY-SIDE COMPARISON │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ Red (H=25) to Blue (H=230): │
│ │
│ sRGB Path: │
│ ========== │
│ │
│ RED ──── muddy ──── brown ──── purple-ish ──── gray ──── BLUE │
│ │ │ │ │ │ │ │
│ t=0 t=0.2 t=0.4 t=0.6 t=0.8 t=1.0 │
│ │
│ The path is UNPREDICTABLE. Intermediate colors feel "dirty". │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ OKLAB Path: │
│ =========== │
│ │
│ RED ──── pink ──── magenta ──── violet ──── purple ──── BLUE │
│ │ │ │ │ │ │ │
│ t=0 t=0.2 t=0.4 t=0.6 t=0.8 t=1.0 │
│ │
│ The path is PREDICTABLE. Intermediate colors are vibrant and logical. │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ Green (H=130) to Yellow (H=85): │
│ │
│ sRGB Path: │
│ ========== │
│ │
│ GREEN ──── muddy ──── olive ──── brown-ish ──── YELLOW │
│ │ │ │ │ │ │
│ t=0 t=0.25 t=0.5 t=0.75 t=1.0 │
│ │
│ Even nearby colors have uneven transitions! │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ OKLAB Path: │
│ =========== │
│ │
│ GREEN ──── lime ──── chartreuse ──── yellow-green ──── YELLOW │
│ │ │ │ │ │ │
│ t=0 t=0.25 t=0.5 t=0.75 t=1.0 │
│ │
│ Smooth, predictable, visually even steps. │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
Core-to-Core Flow Pattern
When interpolating between two Core Colors (both defined in OKLCH), the system converts to OKLAB for the actual interpolation.Copy
┌───────────────────────────────────────────────────────────────────────────────┐
│ CORE-TO-CORE FLOW INTERPOLATION │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ Scenario: Git branch merge line between two branches │
│ │
│ Core A (main branch): oklch(55% 0.15 230) -- Blue │
│ Core B (feature branch): oklch(55% 0.15 50) -- Orange │
│ │
│ Method: color-mix(in oklab, coreA, coreB 50%) │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ Conversion Process: │
│ =================== │
│ │
│ 1. Core A (OKLCH) --> Convert --> OKLAB │
│ oklch(55% 0.15 230) │
│ L=55, C=0.15, H=230deg │
│ │ │
│ v │
│ L=0.55, a=-0.10, b=-0.11 │
│ │
│ 2. Core B (OKLCH) --> Convert --> OKLAB │
│ oklch(55% 0.15 50) │
│ L=55, C=0.15, H=50deg │
│ │ │
│ v │
│ L=0.55, a=0.10, b=0.11 │
│ │
│ 3. Interpolate in OKLAB: │
│ L = 0.55 * 0.5 + 0.55 * 0.5 = 0.55 │
│ a = -0.10 * 0.5 + 0.10 * 0.5 = 0.00 │
│ b = -0.11 * 0.5 + 0.11 * 0.5 = 0.00 │
│ │
│ 4. Result: L=0.55, a=0, b=0 (NEUTRAL GRAY at 55% lightness) │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ Important Clarification: │
│ ======================== │
│ │
│ Blue and Orange are OPPOSITE on the color wheel (180 apart). │
│ Their true midpoint IS neutral. This is mathematically correct. │
│ │
│ The DIFFERENCE from sRGB: │
│ - sRGB midpoint: Often muddy brown (uncontrolled desaturation) │
│ - OKLAB midpoint: Clean neutral gray (predictable, intentional) │
│ │
│ For non-opposite colors, OKLAB maintains chroma: │
│ Core A: oklch(55% 0.15 230) -- Blue │
│ Core C: oklch(55% 0.15 160) -- Cyan-Green │
│ Midpoint: oklch(55% ~0.15 195) -- Maintains chroma, hue averages! │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
Perceptual Distance
In a perceptually uniform color space, equal numerical distances correspond to equal perceived differences.Copy
┌───────────────────────────────────────────────────────────────────────────────┐
│ PERCEPTUAL DISTANCE FORMULA │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ Delta E (Euclidean distance in OKLAB): │
│ │
│ dE = sqrt( (L2-L1)^2 + (a2-a1)^2 + (b2-b1)^2 ) │
│ │
│ Where: │
│ - L1, L2 = Lightness values │
│ - a1, a2 = Green-Red axis values │
│ - b1, b2 = Blue-Yellow axis values │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ Property: If dE values are equal, perceived differences are equal. │
│ │
│ This means: │
│ - Interpolating produces evenly spaced visual steps │
│ - No "faster" or "slower" regions in color transitions │
│ - The gradient feels smooth and natural │
│ │
├───────────────────────────────────────────────────────────────────────────────┤
│ │
│ In sRGB, this property does NOT hold: │
│ │
│ - Moving from dark to mid-gray feels different than mid-gray to light │
│ - Yellow appears much brighter than blue at same sRGB value │
│ - Saturation changes unpredictably across hues │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
THE CENTER
Why Clean Interpolation Matters for Information Flow
In Human-AI collaboration, color gradients communicate relationships between data points. When a merge line connects two branches, the gradient color should intuitively show “this belongs to both.” Muddy sRGB interpolation obscures this relationship, making the user’s cognitive load heavier.Copy
Connection to Core-Flow:
├── Core colors define identity (OKLCH)
├── Flow colors show relationships (OKLAB interpolation)
├── Clean gradients = clear communication of connections
└── Muddy gradients = confusing, broken visual language
CSS Gradients
Using linear-gradient with OKLAB interpolation