ember

A nearly monochrome warm graphite color theme with muted olive, gold, and steel tones โ€” and one vivid coral ember that catches your eye. Three flavors for every lighting mood.

Coral
Orange
Gold
Olive
Sage
Steel
Rose
Mauve
deep-merge.ts
export function deepMerge<T extends PlainObject>(
target: T,
source: Partial<T>,
): T {
const result = { ...target } as T;
for (const key of Object.keys(source) as Array<keyof T>) {
const sourceValue = source[key];
const targetValue = target[key];
// Skip undefined values in source
if (sourceValue === undefined) {
continue;
}
// If both are plain objects, merge recursively
if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
result[key] = deepMerge(
targetValue as PlainObject,
sourceValue as PlainObject,
) as T[keyof T];
} else {
result[key] = sourceValue as T[keyof T];
}
}
return result;
}
Ember
deep-merge.ts
export function deepMerge<T extends PlainObject>(
target: T,
source: Partial<T>,
): T {
const result = { ...target } as T;
for (const key of Object.keys(source) as Array<keyof T>) {
const sourceValue = source[key];
const targetValue = target[key];
// Skip undefined values in source
if (sourceValue === undefined) {
continue;
}
// If both are plain objects, merge recursively
if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
result[key] = deepMerge(
targetValue as PlainObject,
sourceValue as PlainObject,
) as T[keyof T];
} else {
result[key] = sourceValue as T[keyof T];
}
}
return result;
}
Ember Soft
deep-merge.ts
export function deepMerge<T extends PlainObject>(
target: T,
source: Partial<T>,
): T {
const result = { ...target } as T;
for (const key of Object.keys(source) as Array<keyof T>) {
const sourceValue = source[key];
const targetValue = target[key];
// Skip undefined values in source
if (sourceValue === undefined) {
continue;
}
// If both are plain objects, merge recursively
if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
result[key] = deepMerge(
targetValue as PlainObject,
sourceValue as PlainObject,
) as T[keyof T];
} else {
result[key] = sourceValue as T[keyof T];
}
}
return result;
}
Ember Light
Philosophy

Three decisions.

ร—1

One ember among ashes

Eight accent colors, but only one is vivid. Coral handles keywords, cursor, and search highlights. Everything else stays quiet.

H42ยฐ

One hue tints everything

Background, foreground, borders โ€” all tinted with the same warm amber. Not a neutral grey base with colors dropped on top. One hue, root to tip.

L*58

Perceptual balance

All accents sit in the same CIELAB lightness band. No color reads as louder than another โ€” you see structure, not a palette.

Flavors

Dark, soft, light.

Same palette. Three background temperatures.

Ember
def ember_glow(warmth):
"One vivid coral spark."
palette = load("graphite")
return palette.ignite(0.85)
Ember Soft
def ember_glow(warmth):
"One vivid coral spark."
palette = load("graphite")
return palette.ignite(0.85)
Ember Light
def ember_glow(warmth):
"One vivid coral spark."
palette = load("graphite")
return palette.ignite(0.85)
Anatomy

Where Ember lives.

Temperature on x, saturation on y. Most themes cluster cool and saturated. Ember doesn't.

CoolWarmMutedVivid
Ember
Solarized
Nord
Catppuccin
Gruvbox
Everforest
Temperature โ†’โ†‘ Saturation

The Ramp

One continuous gradient from deepest shadow to foreground. Same warm hue, edge to edge.

base0
bg
base2
base3
base4
base5
base6
base7
base8
fg-alt
fg

11 stops ยท H42ยฐ ยท S8โ€“12% ยท L5% โ†’ L82%

Perceptual Cluster

Eight accents plotted by CIELAB lightness โ€” the scale your eye actually perceives. Most cluster in a tight 9-unit band.

55
56
58
59
61
62
63
74
RoseMauveSageSteelOliveCoralOrangeGold

L*55โ€“74 ยท 7 of 8 within a 9-unit band

Get it

Available on GitHub.