Ember: Redshift-veilige kleurpaletten
Introductie
Ember is ontwikkeld om het probleem op te lossen waarbij traditionele kleurpaletten kleuren bevatten die ononderscheidbaar worden zodra redshift wordt ingeschakeld. Het primaire doel van Ember is het bieden van onderscheidende kleuren onder verschillende kleurtemperatuurfilters. Pas wanneer dit is gerealiseerd, worden de door redshift gedegradeerde kleurkanalen verder aangepast om de visuele scheiding overdag (dus zonder redshift) te verbeteren.
Ember bevat vier kleurpaletten:
- 3400K Dark
- 3400K Light
- 2000K Dark
- 1200K Dark
De 3400K-paletten zijn geschikt voor algemeen gebruik en zijn ongeveer geoptimaliseerd voor het maximale Nightshift-effect van een Macbook. De 2000K- en 1200K-paletten zijn meer gespecialiseerd voor liefhebbers van "diepe redshift" (en astronomen).
Een interessant gevolg is dat de extreme redshift-paletten (2000K en 1200K) minder verschillende kleuren hebben dan de 3400K-paletten. Dit komt doordat dergelijke extreme redshifts de omvang van de perceptuele kleurruimte waaruit kleuren kunnen worden gekozen, drastisch verminderen.
Elk palet is gecreëerd in opgegeven (commanded) sRGB, geverifieerd onder de gemodelleerde per-kanaal RGB-winsten van de corresponderende kleurtemperatuur, en geëxporteerd als terminalthema's (Alacritty, iTerm2, Windows Terminal), UI-oppervlakkerollen, categorische grafiekkleuren, een sequentiële kaart met 256 monsters, en Matplotlib/CSS/JSON/Python-artefacten.
Let op: Ember past het filter niet zelf toe; blijf de software gebruiken die u al gebruikt.
De vier paletten
- 3400K Dark
- 3400K Light
- 2000K Dark
- 1200K Dark
Dit zijn de exacte opgegeven sRGB-waarden in elke export. Hoe dieper het doelfilter, hoe minder kleuridentiteiten een familie bevat; aliased terminal-slots worden gelabeld als aliassen in plaats van als extra kleuren.
Met en zonder redshift
- In een terminal
- In grafieken en heatmaps
Waarom identiteiten verdwijnen
Een warm filter is geen getinte overlay: het vermenigvuldigt rood, groen en blauw met verschillende winstfactoren (gains). Bij het 1200K-model van Ember is de blauwe winst nul, waardoor kleuren die alleen in blauw verschillen, een identiek resultaat geven. De diepere paletten bevatten daarom minder identiteiten, in plaats van te doen alsof aliased kleuren onderscheidend blijven.
Gefilterde rijen zijn deterministische signaalsimulaties — opgegeven sRGB vermenigvuldigd met de gepubliceerde winstfactoren van elk profiel. Het zijn geen foto's, gekalibreerde fysieke kleurtemperaturen of voorspellingen van elke display-pipeline. Schakel elk actief warm filter uit voordat u ze beoordeelt, anders past uw scherm de transformatie een tweede keer toe.
Kies een profiel
| Palet | Gebruik het wanneer... | Onderscheidende categorieën |
|---|---|---|
| 3400k-dark | u een bijna-zwart algemeen warm thema wilt | 6 |
| 3400k-light | u een matige warme verschuiving op een licht oppervlak gebruikt | 6 |
| 2000k-dark | u Redshift rond de 2000 K draait | 4 |
| 1200k-dark | u een extreem 1200 K stresstprofiel wilt | 3 |
Begin met 3400k-dark, tenzij u bewust een dieper filter gebruikt. De 2000K- en 1200K-profielen zijn alleen beschikbaar voor donkere modi, omdat een gefilterd licht canvas verandert in een groot oranje-rood veld.
Ember implementeren
1. De bestanden verkrijgen
Voor terminalthema's, CSS en het JSON-manifest:
git clone https://github.com/carpdiem/ember.git
cd ember
Python-gebruikers kunnen de clone overslaan en direct vanaf GitHub installeren:
python -m pip install "ember-palettes @ git+https://github.com/carpdiem/ember.git"
2. Een terminalthema importeren
Alacritty: kopieer één bestand uit themes/terminal/alacritty/ naar uw config-map, en importeer het vervolgens in alacritty.toml:
mkdir -p ~/.config/alacritty/themes
cp themes/terminal/alacritty/2000k-dark.toml ~/.config/alacritty/themes/
[general]
import = ["~/.config/alacritty/themes/2000k-dark.toml"]
iTerm2: open Settings → Profiles → Colors → Color Presets… → Import… en kies een bestand uit themes/terminal/iterm2/.
Windows Terminal: open Settings → Open JSON file, kopieer één object uit themes/terminal/windows-terminal/ naar de root schemes array, en stel vervolgens de colorScheme van uw profiel in op de exacte naam.
De terminal-gids bevat de volledige importstappen en legt uit hoe gereduceerde ANSI-banken zich gedragen onder de diepe profielen.
3. De UI-oppervlakkerollen gebruiken
Elke familie biedt dezelfde geordende rollen aan in JSON, CSS en de Python surfaces() API:
| Rol | Beoogd gebruik |
|---|---|
bg_0 | basis canvas van de applicatie |
bg_1 | aangrenzende regio met lage nadruk of zijbalk |
bg_2 | gewoon paneel of kaart |
bg_3 | genest paneel, actieve besturing of hover-status |
bg_4 | zwevend paneel, menu of popover |
bg_5 | geselecteerde rij, bereik of gefocuste regio |
fg_0 | primaire tekst en essentiële labels |
fg_1 | grotere ondersteunende tekst of graphics; geen normale hoofdtekst |
fg_2 | gedempte, niet-essentiële metadata of decoratie |
De zes achtergronden vormen een monotone ladder. bg0 is altijd het canvas en bg5 is de sterkste achtergrondstatus: donkere families worden lichter richting bg5, terwijl de lichte familie donkerder wordt richting bg5.
Matplotlib
import matplotlib.pyplot as plt
from ember import categorical, categorical_norm, encode_categories, sequential, surfaces
palette = "2000k-dark"
ui = surfaces(palette)
labels = ["control", "alpha", "beta", "gamma"]
order = ["control", "alpha", "beta", "gamma"]
category_ids = encode_categories(labels, order, slug=palette)
fig, (points, image) = plt.subplots(1, 2)
fig.patch.set_facecolor(ui["bg_0"])
points.set_facecolor(ui["bg_2"])
image.set_facecolor(ui["bg_3"])
points.scatter(
[1, 2, 3, 4],
[1.2, 2.4, 1.8, 3.1],
c=category_ids,
cmap=categorical(palette),
norm=categorical_norm(palette),
)
image.imshow([[0.0, 0.4], [0.7, 1.0]], cmap=sequential(palette))
plt.show()
Geef de palette slug door aan categoricalnorm() en encodecategories() zodat hun capaciteitscontroles overeenkomen met de geselecteerde familie. Sequentiële kaarten bieden altijd 256 canonieke float-monsters, onafhankelijk van het aantal categorische kleuren.
CSS
Laad ember.css en selecteer vervolgens een familie:
<link rel="stylesheet" href="/path/to/ember.css">
<section data-ember-palette="3400k-dark">
<div class="panel">...</div>
</section>
[data-ember-palette] {
color: var(--ember-fg-0);
background: var(--ember-bg-0);
}
.panel {
background: var(--ember-bg-2);
}
.panel:hover {
background: var(--ember-bg-3);
}
.popover {
background: var(--ember-bg-4);
}
.selected {
background: var(--ember-bg-5);
}
.series-a {
color: var(--ember-category-one);
}
.heatmap-key {
background: var(--ember-sequential);
}
CSS biedt elf representatieve 8-bit gradiëntstops. Het JSON-manifest en het Python-pakket behouden alle 256 canonieke float-monsters, samen met oppervlakken, categorische kleuren, ANSI-slots, winstprofielen en gemeten resultaten.
Wat is de wetenschap hierachter?
1. Het signaal modelleren dat het display bereikt
Ember benadert een warme display-transformatie als een onafhankelijke winst op elk sRGB-kanaal: display RGB ≈ commanded RGB × [red gain, green gain, blue gain]
| Profiel | RGB winstfactoren | Basis |
|---|---|---|
| 3400k | [1.00, 0.74, 0.53] | warm-wit engineering surrogaat |
| 2000k | [1.0000, 0.5436, 0.0868] | pinned Redshift 2000 K signaal LUT |
| 1200k | [1.0000, 0.3094, 0.0000] | pinned Redshift 1200 K signaal LUT |
Bij 1200 K draagt blauw niets bij aan de gemodelleerde output. Bij 2000 K overleeft slechts 9%. Gewone sRGB-afstand is daarom een slechte proxy voor onderscheid in de nacht: twee kleuren kunnen overdag ver uit elkaar liggen en convergeren na filtering.
Dit zijn software-signaalmodellen, geen gekalibreerde fysieke kleurtemperaturen. Een echt resultaat hangt ook af van het display, besturingssysteem, kalibratie, helderheid en omgevingslicht.
2. Eerst de beperkte staat oplossen
Ember behandelt dag en nacht als twee weergaven van dezelfde opgegeven kleur. Het middelt hun kwaliteit niet tot één score, omdat uitstekende dagelijkse spreiding een nachtelijke botsing niet kan compenseren.
- Stel harde getransformeerde doelen in voor contrast, lichtheid/chroma-geometrie en minimale perceptuele scheiding in Oklab.
- Kies uit de opgegeven kleuren die deze doelen reproduceren een set met een matige chroma voor overdag met sterke ongefilterde scheiding.
Dit draait de gebruikelijke workflow om. Bij 1200 K kan het wijzigen van alleen blauw de getransformeerde kleur niet verstoren, waardoor Ember dat anders verloren kanaal kan gebruiken om de identiteit overdag te verbeteren.
3. Frequente pixels neutraal houden en kleur reserveren voor betekenis
Het menselijk gezichtsvermogen transporteert fijne ruimtelijke details sterker via luminantiekanalen dan via chromatische kanalen. Verzadigde glyphs en tegenovergestelde tinten zijn daarom ongeschikte plekken om een beperkt nachtelijk kleurgamma te gebruiken.
Ember plaatst de meeste pixels in warm-neutrale oppervlakken, gebruikt crème in plaats van puur wit voor hoofdtekst, en reserveert hogere chroma voor semantische accenten.
4. Identiteit beschermen met zowel kleur als structuur
Ember ondersteunt zes categorische identiteiten bij 3400 K, vier bij 2000 K en drie bij 1200 K. Diepe terminalthema's herhalen deze capaciteiten over de zestien ANSI-slots; niet-ondersteunde namen worden bewust als alias behandeld.
Kleur is nog steeds niet genoeg voor kritieke identiteit. Grafieken moeten dit combineren met directe labels, positie, stippellijnpatronen, markervormen of texturen.
5. Continue kaarten spreiden in de getransformeerde weergave
Elke sequentiële kaart begint met een door mensen gekozen pad in aardetinten. De generator maakt dit pad vloeiend in Oklab, meet de cumulatieve afstand na de doeltitransformatie en bemonstert het opnieuw met gelijke getransformeerde-afstand-intervallen.
Het resultaat is een kaart met 256 monsters met strikt monotone getransformeerde lichtheid en bijna gelijke gemodelleerde getransformeerde Oklab-stappen.
Gemeten eigenschappen en exacte release-gates
Gemeten eigenschappen
$\Delta E_{OK}$ hieronder is de Euclidische Oklab-afstand vermenigvuldigd met 100.
| Familie | Categorieën | Dag min $\Delta E_{OK}$ | Getransf. min $\Delta E_{OK}$ | Gem. / max raw chroma | Getransf. L bereik | Min ANSI contrast |
|---|---|---|---|---|---|---|
| 3400K Dark | 6 | 15.00 | 11.45 | 0.0971 / 0.1045 | 0.2206 | 5.29:1 |
| 3400K Light | 6 | 16.73 | 12.41 | 0.0988 / 0.1037 | 0.3068 | 4.65:1 |
| 2000K Dark | 4 | 17.00 | 12.91 | 0.0956 / 0.1082 | 0.1538 | 4.52:1 |
| 1200K Dark | 3 | 20.72 | 10.25 | 0.1047 / 0.1107 | 0.1268 | 4.55:1 |
Categorie- en achtergrondcontrast:
| Familie | Dag min. hue gap | Target | Getransf. cat / bg_0 | Target |
|---|---|---|---|---|
| 3400K Dark | 20.23° | $\ge 20°$ | 3.01:1 | $\ge 3:1$ |
| 3400K Light | 31.75° | $\ge 30°$ | 3.03:1 | $\ge 3:1$ |
| 2000K Dark | 27.76° | $\ge 20°$ | 3.05:1 | $\ge 3:1$ |
| 1200K Dark | 65.03° | $\ge 45°$ | 3.12:1 | $\ge 3:1$ |
Terminal-bank separatie (accenten versus foreground):
| Familie | Dag accent min | Dag → fg_0 | Dag → fg_1 | Dag → fg_2 | Getransf. accent min | Getransf. → fg_0 | Getransf. → fg_1 | Getransf. → fg_2 |
|---|---|---|---|---|---|---|---|---|
| 3400K Dark | 10.76 | 8.67 | 8.13 | 11.72 | 7.31 | 6.58 | 5.16 | 10.18 |
| 3400K Light | 15.64 | 9.25 | 9.47 | 9.22 | 11.07 | 6.47 | 6.70 | 7.13 |
| 2000K Dark | 12.62 | 13.73 | 10.86 | 8.25 | 7.75 | 7.64 | 5.03 | 4.75 |
| 1200K Dark | 12.35 | 9.69 | 8.96 | 14.68 | 4.13 | 4.49 | 4.13 | 11.29 |
Foreground coherentie:
| Familie | fg0 / fg1 / fg_2 | Dag adj. stappen | Getransf. adj. stappen | Dag / getransf. gap ratio | Dag / getransf. hue span | Max dag chroma |
|---|---|---|---|---|---|---|
| 3400K Dark | #DDD0B2 / #BDAE93 / #908472 | 10.41 / 13.79 | 8.70 / 11.88 | 0.7568 / 0.7372 | 9.32° / 2.63° | 0.0426 |
| 3400K Light | #342F2C / #4D4540 / #665C54 | 8.75 / 8.52 | 7.55 / 7.32 | 0.9734 / 0.9707 | 0.00° / 1.12° | 0.0181 |
| 2000K Dark | #EED5AE / #D3BB99 / #AA9D8B | 8.04 / 10.44 | 6.20 / 9.04 | 0.7886 / 0.7008 | 2.74° / 2.51° | 0.0584 |
| 1200K Dark | #FFE5BD / #CBAF89 / #A18C73 | 16.43 / 11.79 | 11.07 / 9.16 | 0.7099 / 0.8177 | 6.73° / 0.71° | 0.0607 |
Donkere oppervlakte metingen (WCAG sRGB relatieve luminantie):
| Donkere familie | bg_0 | Opgegeven luminantie, bg0 → bg5 | Getransf. fg_0 contrast bereik |
|---|---|---|---|
| 3400K Dark | #090807 | 0.00247 → 0.02019 | 6.83–8.52:1 |
| 2000K Dark | #070504 | 0.00162 → 0.01852 | 5.86–6.98:1 |
| 1200K Dark | #060302 | 0.00108 → 0.01571 | 5.32–6.08:1 |
De build reproduceren
uv sync --extra dev
uv run python tools/build_all.py --check
uv run pytest -q
uv run ruff check src tests tools examples
uv build
De release-gates handhaven onder andere:
- Exact vier paletfamilies met categorische capaciteiten 6, 6, 4, 3.
- Categorische opgegeven gemiddelde Oklab chroma tussen 0.09 en 0.105, met geen kleur boven 0.111.
- Minimale afstandsvloeren in zowel ongeverschuifde als getransformeerde staten.
- Terminal dag/nacht capaciteiten 6/6, 6/6, 4/4, 3/3.
- Maximaal 0.15 $\Delta E_{OK}$ tussen elk geautoriseerd getransformeerd accentdoel en de getransformeerde geserialiseerde kleur.
- Minimaal 4.5:1 getransformeerd contrast voor foreground-capabele ANSI-slots tegen de terminal basisachtergrond (
bg_0). - Getransformeerde contrastvloeren van 4.5:1, 3.5:1 en 2.4:1 voor
fg0,fg1enfg_2op elk oppervlak. - Donkere modus opgegeven relatieve luminantie-caps van 0.003, 0.005, 0.009, 0.013, 0.020 en 0.021 over de zes-staps ladder.
- 256 unieke float-monsters per sequentiële kaart, met monotone lichtheid in beide display-staten.
Referenties
- Gruvbox: origineel project en palet: warme neutralen, voldoende contrast en terughoudende accenten voor langdurig gebruik.
- MIT 6.813, Color: chromatische aberratie, de lagere ruimtelijke resolutie van chromatische kanalen, schaarse kleur en terughoudende verzadiging.
- Fan et al. (2024), The Effect of Ambient Illumination and Text Color on Visual Fatigue under Negative Polarity: gecontroleerd bewijs dat tekstkleur en omgevingsverlichting visuele vermoeidheid beïnvloeden bij donkere polariteit.
- EU Data Visualisation Guide, Colour for categories: beperk het aantal categorieën en vermijd oververzadigde of heldere kleuren.
- Datawrapper, What to consider when choosing colors: gebruik neutrale structuren, bescherm contrast van kleine tekst en voeg redundante signalen toe.
- Redshift: pinned kleur-ramp implementatie en temperatuur tabel.
- Björn Ottosson: Een perceptuele kleurruimte voor beeldbewerking.
- Matplotlib: Choosing Colormaps.
- W3C WAI: Understanding contrast minimum.
Licentie
MIT © 2026 Michael Woods.
Groetjes,