base16 theme picker
Which looks better, one or two? Pick the scheme you prefer and each round halves the field, grouping similar schemes together so you narrow in on a look rather than a coin toss.
Round 1Remaining 303About 9 rounds to go
dan@desktop ~/demo $ ls -la total 657 drwxr-xr-x 7 dan users 4096 Nov 1 21:04 . drwxr-xr-x 18 dan users 4096 Oct 28 09:12 .. drwxr-xr-x 8 dan users 4096 Nov 1 20:58 .git -rw-r--r-- 1 dan users 231 Oct 30 17:45 .gitignore drwxr-xr-x 2 dan users 4096 Oct 29 14:02 assets -rwxr-xr-x 1 dan users 604 Oct 31 22:15 build.sh -rw-r--r-- 1 dan users 13420 Oct 30 17:45 Cargo.lock -rw-r--r-- 1 dan users 412 Oct 30 17:45 Cargo.toml lrwxrwxrwx 1 dan users 18 Oct 29 14:05 config -> assets/config.toml -rw-r--r-- 1 dan users 1812 Oct 30 17:45 flake.lock -rw-r--r-- 1 dan users 958 Oct 30 17:45 flake.nix -rw-r--r-- 1 dan users 1071 Oct 12 11:20 LICENSE -rw-r--r-- 1 dan users 88340 Oct 29 14:02 logo.png -rw-r--r-- 1 dan users 2210 Nov 1 19:33 README.md -rw-r--r-- 1 dan users 512000 Oct 20 08:31 release.tar.gz drwxr-xr-x 4 dan users 4096 Nov 1 21:04 src drwxr-xr-x 3 dan users 4096 Nov 1 18:40 target
dan@desktop ~/demo $ eza --icons assets Cargo.toml flake.nix README.md target build.sh config LICENSE release.tar.gz Cargo.lock flake.lock logo.png src
// Debounce a function so it only fires once input settles.
import { readFile } from "node:fs/promises";
const DELAY_MS = 250;
export function debounce(fn, wait = DELAY_MS) {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), wait);
};
}
class Palette extends Map {
static async load(path) {
const text = await readFile(path, "utf8");
const pairs = text.match(/base0[0-9A-F]: "#\w{6}"/g) ?? [];
return new Palette(pairs.map((p) => p.split(": ")));
}
}
const log = debounce((msg) => console.log(`[${new Date().toISOString()}] ${msg}\n`));
log(true ? "ready" : null);using System;
using System.Collections.Generic;
using System.Linq;
namespace Fragment.Engine;
/// <summary>A component that can be attached to an entity.</summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ComponentAttribute(string name) : Attribute
{
public string Name { get; } = name;
}
[Component("transform")]
public record struct Transform(float X, float Y, float Rotation = 0f);
public static class Registry
{
private static readonly Dictionary<string, Type> _types = new();
public static int Scan()
{
foreach (var type in typeof(Registry).Assembly.GetTypes())
{
if (type.GetCustomAttributes(typeof(ComponentAttribute), false)
.FirstOrDefault() is ComponentAttribute attr)
_types[attr.Name] = type;
}
Console.WriteLine($"Found {_types.Count} components\n");
return _types.Count;
}
}use std::collections::HashMap;
use std::fmt;
/// A single 24-bit colour from a Base16 palette.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rgb(u8, u8, u8);
impl fmt::Display for Rgb {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02x}{:02x}{:02x}", self.0, self.1, self.2)
}
}
pub fn parse(hex: &str) -> Option<Rgb> {
let hex = hex.strip_prefix('#')?;
let n = u32::from_str_radix(hex, 16).ok()?;
Some(Rgb((n >> 16) as u8, (n >> 8) as u8, n as u8))
}
fn main() {
let mut palette: HashMap<&'static str, Rgb> = HashMap::new();
for (slot, value) in [("base00", "#1d2021"), ("base08", "#fb4934")] {
if let Some(rgb) = parse(value) {
palette.insert(slot, rgb);
}
}
println!("{} colours loaded\n", palette.len());
}#!/usr/bin/env bash
# Rebuild the system and keep the last few generations.
set -euo pipefail
readonly FLAKE="${HOME}/.nixos"
KEEP=${KEEP:-5}
log() {
printf '\e[1;34m==>\e[0m %s\n' "$*"
}
if [[ $EUID -eq 0 ]]; then
echo "Run this as your user, not root." >&2
exit 1
fi
log "Rebuilding from $FLAKE"
sudo nixos-rebuild switch --flake "$FLAKE#$(hostname)"
for gen in $(nix-env --list-generations -p /nix/var/nix/profiles/system | awk '{print $1}' | head -n -"$KEEP"); do
log "Removing generation $gen"
sudo nix-env --delete-generations -p /nix/var/nix/profiles/system "$gen"
done
case "${1:-}" in
--gc) sudo nix-collect-garbage -d ;;
*) log "Done. Pass --gc to collect garbage." ;;
esac#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SLOT_COUNT 16
typedef struct {
const char *name;
unsigned int rgb[SLOT_COUNT];
} scheme_t;
/* Parse "#rrggbb" into a packed integer, or return -1. */
static long parse_hex(const char *s) {
if (s == NULL || *s != '#' || strlen(s) != 7) {
return -1;
}
char *end;
long value = strtol(s + 1, &end, 16);
return *end == '\0' ? value : -1;
}
int main(int argc, char **argv) {
scheme_t scheme = { .name = "example" };
for (int i = 1; i < argc && i <= SLOT_COUNT; ++i) {
long v = parse_hex(argv[i]);
scheme.rgb[i - 1] = v < 0 ? 0x000000 : (unsigned int)v;
}
printf("%s: base00 = #%06x\n", scheme.name, scheme.rgb[0]);
return EXIT_SUCCESS;
}#include <algorithm>
#include <array>
#include <format>
#include <iostream>
#include <string_view>
namespace theme {
template <typename T, std::size_t N>
constexpr T average(const std::array<T, N>& values) {
T sum{};
for (const auto& v : values) sum += v;
return sum / static_cast<T>(N);
}
class Swatch final {
public:
explicit Swatch(std::string_view name, float lightness)
: name_{name}, lightness_{lightness} {}
[[nodiscard]] bool is_dark() const noexcept { return lightness_ < 0.5f; }
std::string_view name() const { return name_; }
private:
std::string_view name_;
float lightness_ = 0.0f;
};
} // namespace theme
int main() {
constexpr std::array<float, 4> l{0.12f, 0.18f, 0.84f, 0.96f};
auto swatch = theme::Swatch{"base00", theme::average(l)};
std::cout << std::format("{} is {}\n", swatch.name(), swatch.is_dark() ? "dark" : "light");
};;;; Pick the darkest colour out of a palette.
(defpackage :base16
(:use :cl)
(:export #:luminance #:darkest))
(in-package :base16)
(defparameter *palette*
'((:base00 . #x1d2021) (:base05 . #xd5c4a1) (:base08 . #xfb4934)))
(defun luminance (rgb)
"Approximate perceived brightness of a packed RGB integer."
(let ((r (ldb (byte 8 16) rgb))
(g (ldb (byte 8 8) rgb))
(b (ldb (byte 8 0) rgb)))
(+ (* 0.2126 r) (* 0.7152 g) (* 0.0722 b))))
(defmacro with-slot ((name value) entry &body body)
`(destructuring-bind (,name . ,value) ,entry ,@body))
(defun darkest (&optional (palette *palette*))
(reduce (lambda (a b)
(if (< (luminance (cdr a)) (luminance (cdr b))) a b))
palette))
(with-slot (name value) (darkest)
(format t "~a is #~6,'0x~%" name value))dan@desktop ~/demo $ ls -la total 657 drwxr-xr-x 7 dan users 4096 Nov 1 21:04 . drwxr-xr-x 18 dan users 4096 Oct 28 09:12 .. drwxr-xr-x 8 dan users 4096 Nov 1 20:58 .git -rw-r--r-- 1 dan users 231 Oct 30 17:45 .gitignore drwxr-xr-x 2 dan users 4096 Oct 29 14:02 assets -rwxr-xr-x 1 dan users 604 Oct 31 22:15 build.sh -rw-r--r-- 1 dan users 13420 Oct 30 17:45 Cargo.lock -rw-r--r-- 1 dan users 412 Oct 30 17:45 Cargo.toml lrwxrwxrwx 1 dan users 18 Oct 29 14:05 config -> assets/config.toml -rw-r--r-- 1 dan users 1812 Oct 30 17:45 flake.lock -rw-r--r-- 1 dan users 958 Oct 30 17:45 flake.nix -rw-r--r-- 1 dan users 1071 Oct 12 11:20 LICENSE -rw-r--r-- 1 dan users 88340 Oct 29 14:02 logo.png -rw-r--r-- 1 dan users 2210 Nov 1 19:33 README.md -rw-r--r-- 1 dan users 512000 Oct 20 08:31 release.tar.gz drwxr-xr-x 4 dan users 4096 Nov 1 21:04 src drwxr-xr-x 3 dan users 4096 Nov 1 18:40 target
dan@desktop ~/demo $ eza --icons assets Cargo.toml flake.nix README.md target build.sh config LICENSE release.tar.gz Cargo.lock flake.lock logo.png src
// Debounce a function so it only fires once input settles.
import { readFile } from "node:fs/promises";
const DELAY_MS = 250;
export function debounce(fn, wait = DELAY_MS) {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), wait);
};
}
class Palette extends Map {
static async load(path) {
const text = await readFile(path, "utf8");
const pairs = text.match(/base0[0-9A-F]: "#\w{6}"/g) ?? [];
return new Palette(pairs.map((p) => p.split(": ")));
}
}
const log = debounce((msg) => console.log(`[${new Date().toISOString()}] ${msg}\n`));
log(true ? "ready" : null);using System;
using System.Collections.Generic;
using System.Linq;
namespace Fragment.Engine;
/// <summary>A component that can be attached to an entity.</summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ComponentAttribute(string name) : Attribute
{
public string Name { get; } = name;
}
[Component("transform")]
public record struct Transform(float X, float Y, float Rotation = 0f);
public static class Registry
{
private static readonly Dictionary<string, Type> _types = new();
public static int Scan()
{
foreach (var type in typeof(Registry).Assembly.GetTypes())
{
if (type.GetCustomAttributes(typeof(ComponentAttribute), false)
.FirstOrDefault() is ComponentAttribute attr)
_types[attr.Name] = type;
}
Console.WriteLine($"Found {_types.Count} components\n");
return _types.Count;
}
}use std::collections::HashMap;
use std::fmt;
/// A single 24-bit colour from a Base16 palette.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rgb(u8, u8, u8);
impl fmt::Display for Rgb {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02x}{:02x}{:02x}", self.0, self.1, self.2)
}
}
pub fn parse(hex: &str) -> Option<Rgb> {
let hex = hex.strip_prefix('#')?;
let n = u32::from_str_radix(hex, 16).ok()?;
Some(Rgb((n >> 16) as u8, (n >> 8) as u8, n as u8))
}
fn main() {
let mut palette: HashMap<&'static str, Rgb> = HashMap::new();
for (slot, value) in [("base00", "#1d2021"), ("base08", "#fb4934")] {
if let Some(rgb) = parse(value) {
palette.insert(slot, rgb);
}
}
println!("{} colours loaded\n", palette.len());
}#!/usr/bin/env bash
# Rebuild the system and keep the last few generations.
set -euo pipefail
readonly FLAKE="${HOME}/.nixos"
KEEP=${KEEP:-5}
log() {
printf '\e[1;34m==>\e[0m %s\n' "$*"
}
if [[ $EUID -eq 0 ]]; then
echo "Run this as your user, not root." >&2
exit 1
fi
log "Rebuilding from $FLAKE"
sudo nixos-rebuild switch --flake "$FLAKE#$(hostname)"
for gen in $(nix-env --list-generations -p /nix/var/nix/profiles/system | awk '{print $1}' | head -n -"$KEEP"); do
log "Removing generation $gen"
sudo nix-env --delete-generations -p /nix/var/nix/profiles/system "$gen"
done
case "${1:-}" in
--gc) sudo nix-collect-garbage -d ;;
*) log "Done. Pass --gc to collect garbage." ;;
esac#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SLOT_COUNT 16
typedef struct {
const char *name;
unsigned int rgb[SLOT_COUNT];
} scheme_t;
/* Parse "#rrggbb" into a packed integer, or return -1. */
static long parse_hex(const char *s) {
if (s == NULL || *s != '#' || strlen(s) != 7) {
return -1;
}
char *end;
long value = strtol(s + 1, &end, 16);
return *end == '\0' ? value : -1;
}
int main(int argc, char **argv) {
scheme_t scheme = { .name = "example" };
for (int i = 1; i < argc && i <= SLOT_COUNT; ++i) {
long v = parse_hex(argv[i]);
scheme.rgb[i - 1] = v < 0 ? 0x000000 : (unsigned int)v;
}
printf("%s: base00 = #%06x\n", scheme.name, scheme.rgb[0]);
return EXIT_SUCCESS;
}#include <algorithm>
#include <array>
#include <format>
#include <iostream>
#include <string_view>
namespace theme {
template <typename T, std::size_t N>
constexpr T average(const std::array<T, N>& values) {
T sum{};
for (const auto& v : values) sum += v;
return sum / static_cast<T>(N);
}
class Swatch final {
public:
explicit Swatch(std::string_view name, float lightness)
: name_{name}, lightness_{lightness} {}
[[nodiscard]] bool is_dark() const noexcept { return lightness_ < 0.5f; }
std::string_view name() const { return name_; }
private:
std::string_view name_;
float lightness_ = 0.0f;
};
} // namespace theme
int main() {
constexpr std::array<float, 4> l{0.12f, 0.18f, 0.84f, 0.96f};
auto swatch = theme::Swatch{"base00", theme::average(l)};
std::cout << std::format("{} is {}\n", swatch.name(), swatch.is_dark() ? "dark" : "light");
};;;; Pick the darkest colour out of a palette.
(defpackage :base16
(:use :cl)
(:export #:luminance #:darkest))
(in-package :base16)
(defparameter *palette*
'((:base00 . #x1d2021) (:base05 . #xd5c4a1) (:base08 . #xfb4934)))
(defun luminance (rgb)
"Approximate perceived brightness of a packed RGB integer."
(let ((r (ldb (byte 8 16) rgb))
(g (ldb (byte 8 8) rgb))
(b (ldb (byte 8 0) rgb)))
(+ (* 0.2126 r) (* 0.7152 g) (* 0.0722 b))))
(defmacro with-slot ((name value) entry &body body)
`(destructuring-bind (,name . ,value) ,entry ,@body))
(defun darkest (&optional (palette *palette*))
(reduce (lambda (a b)
(if (< (luminance (cdr a)) (luminance (cdr b))) a b))
palette))
(with-slot (name value) (darkest)
(format t "~a is #~6,'0x~%" name value))ResultVariant Rounds
dan@desktop ~/demo $ ls -la total 657 drwxr-xr-x 7 dan users 4096 Nov 1 21:04 . drwxr-xr-x 18 dan users 4096 Oct 28 09:12 .. drwxr-xr-x 8 dan users 4096 Nov 1 20:58 .git -rw-r--r-- 1 dan users 231 Oct 30 17:45 .gitignore drwxr-xr-x 2 dan users 4096 Oct 29 14:02 assets -rwxr-xr-x 1 dan users 604 Oct 31 22:15 build.sh -rw-r--r-- 1 dan users 13420 Oct 30 17:45 Cargo.lock -rw-r--r-- 1 dan users 412 Oct 30 17:45 Cargo.toml lrwxrwxrwx 1 dan users 18 Oct 29 14:05 config -> assets/config.toml -rw-r--r-- 1 dan users 1812 Oct 30 17:45 flake.lock -rw-r--r-- 1 dan users 958 Oct 30 17:45 flake.nix -rw-r--r-- 1 dan users 1071 Oct 12 11:20 LICENSE -rw-r--r-- 1 dan users 88340 Oct 29 14:02 logo.png -rw-r--r-- 1 dan users 2210 Nov 1 19:33 README.md -rw-r--r-- 1 dan users 512000 Oct 20 08:31 release.tar.gz drwxr-xr-x 4 dan users 4096 Nov 1 21:04 src drwxr-xr-x 3 dan users 4096 Nov 1 18:40 target
dan@desktop ~/demo $ eza --icons assets Cargo.toml flake.nix README.md target build.sh config LICENSE release.tar.gz Cargo.lock flake.lock logo.png src
// Debounce a function so it only fires once input settles.
import { readFile } from "node:fs/promises";
const DELAY_MS = 250;
export function debounce(fn, wait = DELAY_MS) {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), wait);
};
}
class Palette extends Map {
static async load(path) {
const text = await readFile(path, "utf8");
const pairs = text.match(/base0[0-9A-F]: "#\w{6}"/g) ?? [];
return new Palette(pairs.map((p) => p.split(": ")));
}
}
const log = debounce((msg) => console.log(`[${new Date().toISOString()}] ${msg}\n`));
log(true ? "ready" : null);using System;
using System.Collections.Generic;
using System.Linq;
namespace Fragment.Engine;
/// <summary>A component that can be attached to an entity.</summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ComponentAttribute(string name) : Attribute
{
public string Name { get; } = name;
}
[Component("transform")]
public record struct Transform(float X, float Y, float Rotation = 0f);
public static class Registry
{
private static readonly Dictionary<string, Type> _types = new();
public static int Scan()
{
foreach (var type in typeof(Registry).Assembly.GetTypes())
{
if (type.GetCustomAttributes(typeof(ComponentAttribute), false)
.FirstOrDefault() is ComponentAttribute attr)
_types[attr.Name] = type;
}
Console.WriteLine($"Found {_types.Count} components\n");
return _types.Count;
}
}use std::collections::HashMap;
use std::fmt;
/// A single 24-bit colour from a Base16 palette.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rgb(u8, u8, u8);
impl fmt::Display for Rgb {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02x}{:02x}{:02x}", self.0, self.1, self.2)
}
}
pub fn parse(hex: &str) -> Option<Rgb> {
let hex = hex.strip_prefix('#')?;
let n = u32::from_str_radix(hex, 16).ok()?;
Some(Rgb((n >> 16) as u8, (n >> 8) as u8, n as u8))
}
fn main() {
let mut palette: HashMap<&'static str, Rgb> = HashMap::new();
for (slot, value) in [("base00", "#1d2021"), ("base08", "#fb4934")] {
if let Some(rgb) = parse(value) {
palette.insert(slot, rgb);
}
}
println!("{} colours loaded\n", palette.len());
}#!/usr/bin/env bash
# Rebuild the system and keep the last few generations.
set -euo pipefail
readonly FLAKE="${HOME}/.nixos"
KEEP=${KEEP:-5}
log() {
printf '\e[1;34m==>\e[0m %s\n' "$*"
}
if [[ $EUID -eq 0 ]]; then
echo "Run this as your user, not root." >&2
exit 1
fi
log "Rebuilding from $FLAKE"
sudo nixos-rebuild switch --flake "$FLAKE#$(hostname)"
for gen in $(nix-env --list-generations -p /nix/var/nix/profiles/system | awk '{print $1}' | head -n -"$KEEP"); do
log "Removing generation $gen"
sudo nix-env --delete-generations -p /nix/var/nix/profiles/system "$gen"
done
case "${1:-}" in
--gc) sudo nix-collect-garbage -d ;;
*) log "Done. Pass --gc to collect garbage." ;;
esac#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SLOT_COUNT 16
typedef struct {
const char *name;
unsigned int rgb[SLOT_COUNT];
} scheme_t;
/* Parse "#rrggbb" into a packed integer, or return -1. */
static long parse_hex(const char *s) {
if (s == NULL || *s != '#' || strlen(s) != 7) {
return -1;
}
char *end;
long value = strtol(s + 1, &end, 16);
return *end == '\0' ? value : -1;
}
int main(int argc, char **argv) {
scheme_t scheme = { .name = "example" };
for (int i = 1; i < argc && i <= SLOT_COUNT; ++i) {
long v = parse_hex(argv[i]);
scheme.rgb[i - 1] = v < 0 ? 0x000000 : (unsigned int)v;
}
printf("%s: base00 = #%06x\n", scheme.name, scheme.rgb[0]);
return EXIT_SUCCESS;
}#include <algorithm>
#include <array>
#include <format>
#include <iostream>
#include <string_view>
namespace theme {
template <typename T, std::size_t N>
constexpr T average(const std::array<T, N>& values) {
T sum{};
for (const auto& v : values) sum += v;
return sum / static_cast<T>(N);
}
class Swatch final {
public:
explicit Swatch(std::string_view name, float lightness)
: name_{name}, lightness_{lightness} {}
[[nodiscard]] bool is_dark() const noexcept { return lightness_ < 0.5f; }
std::string_view name() const { return name_; }
private:
std::string_view name_;
float lightness_ = 0.0f;
};
} // namespace theme
int main() {
constexpr std::array<float, 4> l{0.12f, 0.18f, 0.84f, 0.96f};
auto swatch = theme::Swatch{"base00", theme::average(l)};
std::cout << std::format("{} is {}\n", swatch.name(), swatch.is_dark() ? "dark" : "light");
};;;; Pick the darkest colour out of a palette.
(defpackage :base16
(:use :cl)
(:export #:luminance #:darkest))
(in-package :base16)
(defparameter *palette*
'((:base00 . #x1d2021) (:base05 . #xd5c4a1) (:base08 . #xfb4934)))
(defun luminance (rgb)
"Approximate perceived brightness of a packed RGB integer."
(let ((r (ldb (byte 8 16) rgb))
(g (ldb (byte 8 8) rgb))
(b (ldb (byte 8 0) rgb)))
(+ (* 0.2126 r) (* 0.7152 g) (* 0.0722 b))))
(defmacro with-slot ((name value) entry &body body)
`(destructuring-bind (,name . ,value) ,entry ,@body))
(defun darkest (&optional (palette *palette*))
(reduce (lambda (a b)
(if (< (luminance (cdr a)) (luminance (cdr b))) a b))
palette))
(with-slot (name value) (darkest)
(format t "~a is #~6,'0x~%" name value))palette
- base00
Default background - base01
Lighter background - base02
Selection background - base03
Comments, invisibles - base04
Dark foreground - base05
Default foreground - base06
Light foreground - base07
Light background - base08
Variables, tags, deleted - base09
Numbers, constants - base0A
Classes, bold - base0B
Strings, inserted - base0C
Support, regex, escapes - base0D
Functions, headings - base0E
Keywords, storage - base0F
Deprecated, embedded
stylix
Drop these into your NixOS (or Home Manager) config to theme everything that Stylix supports with this scheme:
Schemes from tinted-theming/schemes (MIT), as packaged in nixpkgs base16-schemes.