summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 20:30:29 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit799bfc56f75535ad13089ee2fcbe907f91305ca3 (patch)
tree1d22ca52a8fab2cfb90c80926c2df5f001197a98
parent0fd908eea63d04886b269438aa7529d3dd422256 (diff)
downloadpardes-799bfc56f75535ad13089ee2fcbe907f91305ca3.tar.gz
pardes-799bfc56f75535ad13089ee2fcbe907f91305ca3.zip
A theme that names no tint for its tags' names takes one of its own hues, so every theme draws a file's name, a +Name and the Tty word off the tag's ink
The faithful ports (neobones_dark, dracula, tokyonight, the *bones, ...), every helix import and any theme file set no tag_name_fg, and ChromeTheme fell back to the plain ink: their tags lost the accent the pardes themes have. The user's neobones_dark looked tinted before only because the Sep 24 build had no such theme and fell back to orchard. The tint is now derived when unset: the theme's keyword colour, else the first of its ANSI accents, syntax, grip, selection or diagnostic colours with a hue, placed at the lightness nearest the ink where the hue holds, at least 4.5:1 on its ground (half a point under the ink's own where the ink has less to spare) and 0.07 off the ink in OKLab. Each ground, plain and focused, gets its own. A theme that names its tint keeps it exactly. Tests: every theme's chrome keeps both tints off the ink and legible; every theme draws a file's basename, +Errors and Tty+sh in a tint, focused or not. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--src/colors.zig121
-rw-r--r--src/draw.zig54
-rw-r--r--test/gui-goldens.txt8
-rw-r--r--test/panes.zig8
-rw-r--r--test/snapshots/theme.golden12
-rw-r--r--test/snapshots/themesel.golden18
6 files changed, 189 insertions, 32 deletions
diff --git a/src/colors.zig b/src/colors.zig
index 72e25aed..b6da0d2c 100644
--- a/src/colors.zig
+++ b/src/colors.zig
@@ -153,18 +153,22 @@ fn toOklab(rgb: [3]u8) [3]f32 {
};
}
-fn fromOklab(lab: [3]f32) [3]u8 {
+fn oklabLinear(lab: [3]f32) [3]f32 {
const lm = lab[0] + 0.3963377774 * lab[1] + 0.2158037573 * lab[2];
const mm = lab[0] - 0.1055613458 * lab[1] - 0.0638541728 * lab[2];
const sm = lab[0] - 0.0894841775 * lab[1] - 1.2914855480 * lab[2];
const l = lm * lm * lm;
const m = mm * mm * mm;
const s = sm * sm * sm;
- const linear: [3]f32 = .{
+ return .{
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
};
+}
+
+fn fromOklab(lab: [3]f32) [3]u8 {
+ const linear = oklabLinear(lab);
var out: [3]u8 = undefined;
for (&out, linear) |*o, v| {
const x = std.math.clamp(v, 0, 1);
@@ -181,6 +185,72 @@ pub fn mixOklab(a: [3]u8, b: [3]u8, t: f32) [3]u8 {
return fromOklab(.{ x[0] + (y[0] - x[0]) * t, x[1] + (y[1] - x[1]) * t, x[2] + (y[2] - x[2]) * t });
}
+fn oklabDistance(a: [3]f32, b: [3]f32) f32 {
+ return @sqrt((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1]) + (a[2] - b[2]) * (a[2] - b[2]));
+}
+
+/// How far a name's tint stands off its tag's ink in OKLab: a hue you see
+/// at a glance, not a shade you look for.
+pub const name_tint_distance: f32 = 0.07;
+/// How much hue a colour needs to lend a name its tint.
+const name_tint_chroma: f32 = 0.06;
+
+/// A name's tint for a theme that sets none (the faithful ports, the
+/// imports, a theme file): a hue of the theme's own -- its keyword colour,
+/// else the first of its ANSI accents, syntax, grip, selection or
+/// diagnostic colours that has one -- at the lightness nearest the tag's
+/// ink where that hue holds, the name reads on its ground (nameTintFloor)
+/// and it stands off the ink. Null when no colour of the theme will do.
+fn derivedNameTint(th: *const Theme, ink: [3]u8, ground: [3]u8) ?[3]u8 {
+ const pal = th.palette orelse [_][3]u8{th.kw} ** 16;
+ const sources = [_][3]u8{ th.kw, pal[4], pal[5], pal[6], pal[3], pal[2], pal[1], th.num, th.str, th.box } ++
+ [_][3]u8{ th.column_box orelse th.num, th.box_dirty orelse th.num, th.sel_bg, th.search_bg orelse th.num } ++
+ [_][3]u8{ th.diagnostic_info orelse th.num, th.diagnostic_hint orelse th.num, th.diagnostic_warning orelse th.num } ++
+ // A theme with no hue of its own at all (flatwhite): acme's DMedblue's.
+ [_][3]u8{.{ 0x00, 0x00, 0x99 }};
+ // Near the ink's lightness first, from any of the theme's hues; only
+ // then further off it.
+ for ([2]f32{ 0.15, 1 }) |reach| for (sources) |source| {
+ if (nameTintFrom(toOklab(source), ink, ground, reach)) |tint| return tint;
+ };
+ return null;
+}
+
+fn nameTintFrom(source: [3]f32, ink: [3]u8, ground: [3]u8, reach: f32) ?[3]u8 {
+ const chroma = @sqrt(source[1] * source[1] + source[2] * source[2]);
+ if (chroma < name_tint_chroma) return null;
+ const ink_lab = toOklab(ink);
+ const floor = nameTintFloor(ink, ground);
+ var step: f32 = 0;
+ while (step <= reach) : (step += 0.01) {
+ for ([2]f32{ 1, -1 }) |sign| {
+ const lightness = ink_lab[0] + sign * step;
+ if (lightness < 0 or lightness > 1) continue;
+ // As much of the hue as that lightness holds, up to a tint's.
+ var want = @min(chroma, 0.12);
+ while (want >= 0.04) : (want -= 0.01) {
+ const lab: [3]f32 = .{ lightness, source[1] / chroma * want, source[2] / chroma * want };
+ const in_gamut = for (oklabLinear(lab)) |v| {
+ if (v < -0.001 or v > 1.001) break false;
+ } else true;
+ if (!in_gamut) continue;
+ const tint = fromOklab(lab);
+ if (contrast(tint, ground) < floor) continue;
+ if (oklabDistance(toOklab(tint), ink_lab) < name_tint_distance) continue;
+ return tint;
+ }
+ }
+ }
+ return null;
+}
+
+/// What a name's tint must read at on its ground: 4.5:1, or within half a
+/// point of the ink's own where the ink has less to spare (seoulbones_dark's
+/// near-white on grey leaves no hue at the ink's 4.53:1).
+pub fn nameTintFloor(ink: [3]u8, ground: [3]u8) f32 {
+ return @min(4.5, contrast(ink, ground) - 0.5);
+}
+
test "an OKLab fade holds its ends exactly and keeps a mid-way bright" {
for ([_][3]u8{ .{ 0, 0, 0 }, .{ 255, 255, 255 }, .{ 0x0c, 0x0c, 0x0e }, .{ 0xe8, 0xc4, 0x6a }, .{ 0x1c, 0x51, 0x72 } }) |rgb| {
try std.testing.expectEqual(rgb, fromOklab(toOklab(rgb)));
@@ -548,13 +618,21 @@ pub const ChromeTheme = struct {
pub fn fromTheme(th: *const Theme) ChromeTheme {
// The initial chrome is resolved at compile time: contrast takes pow.
@setEvalBranchQuota(200_000);
+ const active_bg = focusTint(th);
+ const active_fg = th.tag_active_fg orelse th.tag_fg;
+ // A name is always tinted: a theme that names no tint gets one of
+ // its own hues, on each ground. One that names only the plain tint
+ // keeps it when focused too.
+ const name_fg = th.tag_name_fg orelse (derivedNameTint(th, th.tag_fg, th.tag_bg) orelse th.tag_fg);
+ const active_name_fg = th.tag_active_name_fg orelse
+ (th.tag_name_fg orelse (derivedNameTint(th, active_fg, active_bg) orelse active_fg));
return .{
.tag_bg = th.tag_bg,
.tag_fg = th.tag_fg,
- .tag_active_bg = focusTint(th),
- .tag_active_fg = th.tag_active_fg orelse th.tag_fg,
- .tag_name_fg = th.tag_name_fg orelse th.tag_fg,
- .tag_active_name_fg = th.tag_active_name_fg orelse (th.tag_name_fg orelse (th.tag_active_fg orelse th.tag_fg)),
+ .tag_active_bg = active_bg,
+ .tag_active_fg = active_fg,
+ .tag_name_fg = name_fg,
+ .tag_active_name_fg = active_name_fg,
.border = separatorOf(th),
.empty_col = th.empty_col orelse separatorOf(th),
.search_bg = th.search_bg orelse th.sel_bg,
@@ -1108,8 +1186,9 @@ test "legacy ThemeFile documents inherit Pardes UI roles without new fields" {
try std.testing.expectEqual(@as(?[3]u8, null), parsed.search_bg);
const chrome = ChromeTheme.fromTheme(&parsed);
try std.testing.expectEqual(parsed.tag_bg, chrome.tag_active_bg);
- try std.testing.expectEqual(parsed.tag_fg, chrome.tag_name_fg);
- try std.testing.expectEqual(chrome.tag_active_fg, chrome.tag_active_name_fg);
+ // Its names take a tint of its own hues, never the plain ink.
+ try std.testing.expect(!std.meta.eql(parsed.tag_fg, chrome.tag_name_fg));
+ try std.testing.expect(!std.meta.eql(chrome.tag_active_fg, chrome.tag_active_name_fg));
try std.testing.expectEqual(parsed.num, chrome.column_box);
try std.testing.expectEqual(mix(parsed.num, parsed.tag_bg), chrome.column_box_dim);
try std.testing.expectEqual(parsed.sel_bg, chrome.search_bg);
@@ -1148,8 +1227,9 @@ test "custom filename tints preserve optional active fallback precedence" {
theme.tag_name_fg = null;
theme.tag_active_name_fg = null;
var chrome = ChromeTheme.fromTheme(&theme);
- try std.testing.expectEqual(theme.tag_fg, chrome.tag_name_fg);
- try std.testing.expectEqual(theme.tag_active_fg.?, chrome.tag_active_name_fg);
+ // Neither named: each derived, on its own ground.
+ try std.testing.expectEqual(derivedNameTint(&theme, theme.tag_fg, theme.tag_bg).?, chrome.tag_name_fg);
+ try std.testing.expectEqual(derivedNameTint(&theme, theme.tag_active_fg.?, chrome.tag_active_bg).?, chrome.tag_active_name_fg);
theme.tag_name_fg = .{ 120, 130, 140 };
chrome = ChromeTheme.fromTheme(&theme);
try std.testing.expectEqual(theme.tag_name_fg.?, chrome.tag_name_fg);
@@ -1160,6 +1240,25 @@ test "custom filename tints preserve optional active fallback precedence" {
try std.testing.expectEqual(theme.tag_active_name_fg.?, chrome.tag_active_name_fg);
theme.tag_name_fg = null;
chrome = ChromeTheme.fromTheme(&theme);
- try std.testing.expectEqual(theme.tag_fg, chrome.tag_name_fg);
+ try std.testing.expectEqual(derivedNameTint(&theme, theme.tag_fg, theme.tag_bg).?, chrome.tag_name_fg);
try std.testing.expectEqual(theme.tag_active_name_fg.?, chrome.tag_active_name_fg);
}
+
+test "every theme tints its names off the tag's ink, and they read on both grounds" {
+ for (&themes) |*th| {
+ const chrome = ChromeTheme.fromTheme(th);
+ const pairs = [2][3][3]u8{
+ .{ chrome.tag_fg, chrome.tag_name_fg, chrome.tag_bg },
+ .{ chrome.tag_active_fg, chrome.tag_active_name_fg, chrome.tag_active_bg },
+ };
+ for (pairs) |pair| {
+ const ink, const name, const ground = pair;
+ const apart = oklabDistance(toOklab(ink), toOklab(name));
+ const floor = nameTintFloor(ink, ground);
+ if (apart < name_tint_distance or contrast(name, ground) < floor - 0.01)
+ std.debug.print("{s}: name {x} on {x} is {d:.3} off the ink {x}, {d:.2}:1\n", .{ th.name, name, ground, apart, ink, contrast(name, ground) });
+ try std.testing.expect(apart >= name_tint_distance);
+ try std.testing.expect(contrast(name, ground) >= floor - 0.01);
+ }
+ }
+}
diff --git a/src/draw.zig b/src/draw.zig
index 9c9e11dd..10303117 100644
--- a/src/draw.zig
+++ b/src/draw.zig
@@ -1477,6 +1477,60 @@ test "a grip's four states, focused or not and clean or dirty, are each its own
pane.file.?.revision = pane.file.?.saved_revision;
}
+test "every theme draws a file's name, a +Errors and a Tty word in a tint, never the tag's plain ink" {
+ const gpa = std.testing.allocator;
+ const p = try Pardes.init(gpa, .{ .cols = 120, .rows = 40, .tty_only = true });
+ defer p.deinit();
+ _ = try p.setTestFile("text\n");
+ try std.testing.expect(p.executeBuiltinLine(0, "Tty+sh"));
+ const terminal = p.active;
+ p.acknowledgeShell(terminal, "/bin/sh", false);
+ try @import("Output.zig").openErrors(p, 0, try gpa.dupe(u8, "oops\n"));
+ while (p.nextEffect()) |_| {}
+ p.animate_theme_changes = false;
+ const errors = for (p.panes, 0..) |slot, id| {
+ const pane = slot orelse continue;
+ if (pane.file) |f| if (std.mem.endsWith(u8, f.path, "+Errors")) break id;
+ } else return error.NoErrorsPane;
+ const Probe = struct { id: usize, word: []const u8 };
+ const probes = [_]Probe{ .{ .id = 0, .word = "test.txt" }, .{ .id = terminal, .word = "Tty+sh" }, .{ .id = errors, .word = "+Errors" } };
+ var arena: std.heap.ArenaAllocator = .init(gpa);
+ defer arena.deinit();
+ for (pardes.themes, 0..) |theme, index| {
+ colors.setThemeIndex(p, index);
+ // Each pane focused in turn: every word on both grounds.
+ for (probes) |focused| {
+ p.active = focused.id;
+ _ = arena.reset(.retain_capacity);
+ const s = try p.render(arena.allocator());
+ for (probes) |probe| {
+ const r = p.rects[probe.id];
+ const y = p.tagTop(p.panes[probe.id].?, r);
+ // The row's text, and the column each byte of it is drawn at.
+ var row: [512]u8 = undefined;
+ var cols: [512]u16 = undefined;
+ var len: usize = 0;
+ for (r.x..r.x + r.w) |x| {
+ const g = s.at(@intCast(x), y).grapheme();
+ for (if (g.len == 0) " " else g) |byte| if (len < row.len) {
+ row[len] = byte;
+ cols[len] = @intCast(x);
+ len += 1;
+ };
+ }
+ const name = std.mem.indexOf(u8, row[0..len], probe.word) orelse return error.WordNotDrawn;
+ const plain = std.mem.indexOf(u8, row[0..len], " Del") orelse return error.WordNotDrawn;
+ const tint = s.at(cols[name], y).style.fg;
+ const ink = s.at(cols[plain + 1], y).style.fg;
+ if (std.meta.eql(tint, ink)) std.debug.print("{s}: {s} drawn in the tag's ink\n", .{ theme.name, probe.word });
+ try std.testing.expect(!std.meta.eql(tint, ink));
+ // The whole word, in the one tint.
+ try std.testing.expect(std.meta.eql(s.at(cols[name + probe.word.len - 1], y).style.fg, tint));
+ }
+ }
+ }
+}
+
test "a tag of three rows has its grip on the first, and band under it that still grabs the pane" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 30 });
defer p.deinit();
diff --git a/test/gui-goldens.txt b/test/gui-goldens.txt
index 2b364b37..1bc25548 100644
--- a/test/gui-goldens.txt
+++ b/test/gui-goldens.txt
@@ -11,7 +11,7 @@
12-multiline-tag ddd7d775a246e91b88f0eff98b6f9bea8aa5d63dbfbe931bd830ccb9086bd5c4
13-image f609043fc0fcdb55369b836b61fcbc631f786cc2295a5ddd72c0d9a59b177a60
14-theme-ink 881b7e99f38ad4aa3c46d2c61e20598010175d7858712a67fa6638fc29f12e14
-15-theme-acme-light ca5252f5117ac8d6c679cef917c9f316c9ca311fc7a8dcbac2796b4fc04d3b64
-16-debug 591bb7c45b4b9c46cd2458330b4fa3962afc6dac827fa6f5e20e927a72c586e6
-17-terminal 38299427891e31de0b5597159501c161474620eb26fd7708a6396f0fd685209e
-18-mid-transition 5d3e37d874988b460297e18df9508a77b602d5f675e28983c2c793871d4301f6
+15-theme-acme-light dbe607594120020d55a52c5ca67562993c43177e857d75e160ae53aed331299c
+16-debug 6021c10cd242f133b9cff530b869d9c8d52b42461b98e51926e360c08c053593
+17-terminal cb2fc421870ab3987ad1fdb04a9c7833a97887e274633f6a0ac7759ad645f7a5
+18-mid-transition cc271b4f0e8d2e18a59613f3647f143fd5d9e6aa6a0581320c4d3ce6d7c2afcc
diff --git a/test/panes.zig b/test/panes.zig
index c1ae1666..3b2bea0a 100644
--- a/test/panes.zig
+++ b/test/panes.zig
@@ -1554,7 +1554,7 @@ const TagNameTintTests = struct {
try std.testing.expectEqual(pardes.Color{ .rgb = p.theme().sel_fg }, surface.at(x + 1, y).style.fg);
}
- test "tag filename tint legacy themes retain distinct active foreground" {
+ test "tag filename tint legacy themes keep their active foreground and tint the name with a hue of their own" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 70, .rows = 14 });
defer p.deinit();
const pane = try p.setTestFile("body\n");
@@ -1574,8 +1574,12 @@ const TagNameTintTests = struct {
const x = p.rects[0].x + pardes.TAG_TEXT_INSET;
const y = tagY(p, p.rects[0]);
const fg = if (active) active_fg else tag_fg;
+ // The directory in the ink; the name tinted, never in it.
+ const chrome = p.chromeTheme();
+ const tint = if (active) chrome.tag_active_name_fg else chrome.tag_name_fg;
+ try std.testing.expect(!std.meta.eql(tint, fg));
try std.testing.expectEqual(pardes.Color{ .rgb = fg }, surface.at(x + 1, y).style.fg);
- try std.testing.expectEqual(pardes.Color{ .rgb = fg }, surface.at(x + 5, y).style.fg);
+ try std.testing.expectEqual(pardes.Color{ .rgb = tint }, surface.at(x + 5, y).style.fg);
}
}
};
diff --git a/test/snapshots/theme.golden b/test/snapshots/theme.golden
index 622c704b..f4636bc2 100644
--- a/test/snapshots/theme.golden
+++ b/test/snapshots/theme.golden
@@ -523,7 +523,7 @@
== style acme grid=100x31
|0: 0-50 #000000,#eaffff, 51-59 #000000,#9eeeee, 60-99 #000000,#eaffff,
|1: 0-1 d,#8888cc, 2-2 d,#eaffff, 3-49 #000000,#eaffff, 50-51 #000000,#8888cc, 52-99 #000000,#eaffff,
-|2: 0-1 d,#8888cc, 2-2 d,#eaffff, 3-49 #000000,#eaffff, 50-51 #000000,#b9c3e5, 52-99 #000000,#eaffff,
+|2: 0-1 d,#8888cc, 2-2 d,#eaffff, 3-29 #000000,#eaffff, 30-38 #010006,#eaffff, 39-49 #000000,#eaffff, 50-51 #000000,#b9c3e5, 52-79 #000000,#eaffff, 80-88 #010006,#eaffff, 89-99 #000000,#eaffff,
|3: 0-2 d,#eaffff, 3-99 #000000,#eaffff,
|4: 0-1 d,#ffffea, 2-99 #000000,#ffffea,
|5: 0-1 d,#ffffea, 2-99 #000000,#ffffea,
@@ -538,7 +538,7 @@
|14: 0-99 d,#ffffea,
|15: 0-99 d,#ffffea,
|16: 0-99 d,#ffffea,
-|17: 0-1 d,#b9c3e5, 2-2 d,#eaffff, 3-49 #000000,#eaffff, 50-99 #000000,#ffffea,
+|17: 0-1 d,#b9c3e5, 2-2 d,#eaffff, 3-29 #000000,#eaffff, 30-38 #010006,#eaffff, 39-49 #000000,#eaffff, 50-99 #000000,#ffffea,
|18: 0-2 d,#eaffff, 3-49 #000000,#eaffff, 50-99 #000000,#ffffea,
|19: 0-1 d,#ffffea, 2-99 #000000,#ffffea,
|20: 0-1 d,#ffffea, 2-99 #000000,#ffffea,
@@ -589,7 +589,7 @@
== style dark-plus grid=100x31
|0: 0-50 #a7a7a7,#3c3c3c, 51-59 #d4d4d4,#264f78, 60-60 #d4d4d4,#3c3c3c, 61-99 #a7a7a7,#3c3c3c,
|1: 0-1 d,#458276, 2-2 d,#1e1e1e, 3-49 #ffffff,#1e1e1e, 50-51 #ffffff,#458276, 52-52 #ffffff,#3c3c3c, 53-99 #a7a7a7,#3c3c3c,
-|2: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-49 #ffffff,#1e1e1e, 50-51 #ffffff,#1c608e, 52-52 #ffffff,#3c3c3c, 53-99 #a7a7a7,#3c3c3c,
+|2: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-29 #ffffff,#1e1e1e, 30-38 #cbe5fc,#1e1e1e, 39-49 #ffffff,#1e1e1e, 50-51 #ffffff,#1c608e, 52-52 #ffffff,#3c3c3c, 53-79 #a7a7a7,#3c3c3c, 80-88 #68aee9,#3c3c3c, 89-99 #a7a7a7,#3c3c3c,
|3: 0-2 d,#1e1e1e, 3-49 #ffffff,#1e1e1e, 50-52 #ffffff,#3c3c3c, 53-99 #a7a7a7,#3c3c3c,
|4: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 #d4d4d4,#424242, 51-99 #d4d4d4,#1e1e1e,
|5: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 #d4d4d4,#424242, 51-99 #d4d4d4,#1e1e1e,
@@ -604,7 +604,7 @@
|14: 0-0 d,#424242, 1-49 d,#1e1e1e, 50-50 d,#424242, 51-99 d,#1e1e1e,
|15: 0-0 d,#424242, 1-49 d,#1e1e1e, 50-50 d,#424242, 51-99 d,#1e1e1e,
|16: 0-0 d,#424242, 1-49 d,#1e1e1e, 50-50 d,#424242, 51-99 d,#1e1e1e,
-|17: 0-1 d,#1c608e, 2-2 d,#3c3c3c, 3-49 #a7a7a7,#3c3c3c, 50-50 #a7a7a7,#424242, 51-99 #a7a7a7,#1e1e1e,
+|17: 0-1 d,#1c608e, 2-2 d,#3c3c3c, 3-29 #a7a7a7,#3c3c3c, 30-38 #68aee9,#3c3c3c, 39-49 #a7a7a7,#3c3c3c, 50-50 #a7a7a7,#424242, 51-99 #a7a7a7,#1e1e1e,
|18: 0-2 d,#3c3c3c, 3-49 #a7a7a7,#3c3c3c, 50-50 #a7a7a7,#424242, 51-99 #a7a7a7,#1e1e1e,
|19: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 #d4d4d4,#424242, 51-99 #d4d4d4,#1e1e1e,
|20: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-49 #d4d4d4,#1e1e1e, 50-50 #d4d4d4,#424242, 51-99 #d4d4d4,#1e1e1e,
@@ -622,7 +622,7 @@
== style solarized-dark grid=100x31
|0: 0-50 #01090b,#657b83, 51-59 #002b36,#586e75, 60-60 #002b36,#657b83, 61-99 #01090b,#657b83,
|1: 0-1 d,#758a41, 2-2 d,#93a1a1, 3-49 #073642,#93a1a1, 50-51 #073642,#758a41, 52-52 #073642,#657b83, 53-99 #01090b,#657b83,
-|2: 0-1 d,#2483c5, 2-2 d,#93a1a1, 3-49 #073642,#93a1a1, 50-51 #073642,#185986, 52-52 #073642,#657b83, 53-99 #01090b,#657b83,
+|2: 0-1 d,#2483c5, 2-2 d,#93a1a1, 3-29 #073642,#93a1a1, 30-38 #2d3502,#93a1a1, 39-49 #073642,#93a1a1, 50-51 #073642,#185986, 52-52 #073642,#657b83, 53-79 #01090b,#657b83, 80-88 #121600,#657b83, 89-99 #01090b,#657b83,
|3: 0-2 d,#93a1a1, 3-49 #073642,#93a1a1, 50-52 #073642,#657b83, 53-99 #01090b,#657b83,
|4: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 #839496,#839496, 51-99 #839496,#002b36,
|5: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 #839496,#839496, 51-99 #839496,#002b36,
@@ -637,7 +637,7 @@
|14: 0-0 d,#839496, 1-49 d,#002b36, 50-50 d,#839496, 51-99 d,#002b36,
|15: 0-0 d,#839496, 1-49 d,#002b36, 50-50 d,#839496, 51-99 d,#002b36,
|16: 0-0 d,#839496, 1-49 d,#002b36, 50-50 d,#839496, 51-99 d,#002b36,
-|17: 0-1 d,#185986, 2-2 d,#657b83, 3-49 #01090b,#657b83, 50-50 #01090b,#839496, 51-99 #01090b,#002b36,
+|17: 0-1 d,#185986, 2-2 d,#657b83, 3-29 #01090b,#657b83, 30-38 #121600,#657b83, 39-49 #01090b,#657b83, 50-50 #01090b,#839496, 51-99 #01090b,#002b36,
|18: 0-2 d,#657b83, 3-49 #01090b,#657b83, 50-50 #01090b,#839496, 51-99 #01090b,#002b36,
|19: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 #839496,#839496, 51-99 #839496,#002b36,
|20: 0-0 d,#839496, 1-1 d,#002b36, 2-49 #839496,#002b36, 50-50 #839496,#839496, 51-99 #839496,#002b36,
diff --git a/test/snapshots/themesel.golden b/test/snapshots/themesel.golden
index 05a8af39..07aa45d9 100644
--- a/test/snapshots/themesel.golden
+++ b/test/snapshots/themesel.golden
@@ -533,7 +533,7 @@
== style step-acme grid=100x31
|0: 0-99 #000000,#eaffff,
|1: 0-1 d,#8888cc, 2-2 d,#eaffff, 3-99 #000000,#eaffff,
-|2: 0-1 d,#b9c3e5, 2-2 d,#eaffff, 3-99 #000000,#eaffff,
+|2: 0-1 d,#b9c3e5, 2-2 d,#eaffff, 3-32 #000000,#eaffff, 33-38 #010006,#eaffff, 39-99 #000000,#eaffff,
|3: 0-4 d,#ffffea, 5-6 #999980,#ffffea, 7-99 #000000,#ffffea,
|4: 0-4 d,#ffffea, 5-6 #999980,#ffffea, 7-99 #000000,#ffffea,
|5: 0-4 d,#ffffea, 5-6 #999980,#ffffea, 7-99 #000000,#ffffea,
@@ -547,7 +547,7 @@
|13: 0-0 d,#99994c, 1-3 d,#ffffea, 4-6 #999980,#ffffea, 7-99 #000000,#ffffea,
|14: 0-0 d,#99994c, 1-3 d,#ffffea, 4-6 #999980,#ffffea, 7-99 #000000,#ffffea,
|15: 0-0 d,#99994c, 1-3 d,#ffffea, 4-6 #999980,#ffffea, 7-99 #000000,#ffffea,
-|16: 0-1 d,#8888cc, 2-2 d,#eaffff, 3-99 #000000,#eaffff,
+|16: 0-1 d,#8888cc, 2-2 d,#eaffff, 3-32 #000000,#eaffff, 33-40 #010006,#eaffff, 41-99 #000000,#eaffff,
|17: 0-4 d,#ffffea, 5-6 #999980,#ffffea, 7-92 #000000,#ffffea, 93-99 #000000,#eaffff,
|18: 0-0 d,#99994c, 1-4 d,#ffffea, 5-6 #999980,#ffffea, 7-99 #000000,#ffffea,
|19: 0-0 d,#99994c, 1-4 d,#ffffea, 5-6 #999980,#ffffea, 7-99 #000000,#ffffea,
@@ -627,7 +627,7 @@
== style step-dark-plus grid=100x31
|0: 0-99 #a7a7a7,#3c3c3c,
|1: 0-1 d,#458276, 2-2 d,#1e1e1e, 3-99 #ffffff,#1e1e1e,
-|2: 0-1 d,#1c608e, 2-2 d,#3c3c3c, 3-99 #a7a7a7,#3c3c3c,
+|2: 0-1 d,#1c608e, 2-2 d,#3c3c3c, 3-32 #a7a7a7,#3c3c3c, 33-38 #68aee9,#3c3c3c, 39-99 #a7a7a7,#3c3c3c,
|3: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|4: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|5: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
@@ -641,7 +641,7 @@
|13: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|14: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|15: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
-|16: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-99 #ffffff,#1e1e1e,
+|16: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-32 #ffffff,#1e1e1e, 33-40 #cbe5fc,#1e1e1e, 41-99 #ffffff,#1e1e1e,
|17: 0-0 d,#424242, 1-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-92 #d4d4d4,#1e1e1e, 93-93 #d4d4d4,#3c3c3c, 94-99 #a7a7a7,#3c3c3c,
|18: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|19: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
@@ -674,7 +674,7 @@
== style step-solarized-dark grid=100x31
|0: 0-99 #01090b,#657b83,
|1: 0-1 d,#758a41, 2-2 d,#93a1a1, 3-99 #073642,#93a1a1,
-|2: 0-1 d,#185986, 2-2 d,#657b83, 3-99 #01090b,#657b83,
+|2: 0-1 d,#185986, 2-2 d,#657b83, 3-32 #01090b,#657b83, 33-38 #121600,#657b83, 39-99 #01090b,#657b83,
|3: 0-0 d,#839496, 1-4 d,#002b36, 5-6 #586e75,#002b36, 7-99 #839496,#002b36,
|4: 0-0 d,#839496, 1-4 d,#002b36, 5-6 #586e75,#002b36, 7-99 #839496,#002b36,
|5: 0-0 d,#839496, 1-4 d,#002b36, 5-6 #586e75,#002b36, 7-99 #839496,#002b36,
@@ -688,7 +688,7 @@
|13: 0-0 d,#eee8d5, 1-3 d,#002b36, 4-6 #586e75,#002b36, 7-99 #839496,#002b36,
|14: 0-0 d,#eee8d5, 1-3 d,#002b36, 4-6 #586e75,#002b36, 7-99 #839496,#002b36,
|15: 0-0 d,#eee8d5, 1-3 d,#002b36, 4-6 #586e75,#002b36, 7-99 #839496,#002b36,
-|16: 0-1 d,#2483c5, 2-2 d,#93a1a1, 3-99 #073642,#93a1a1,
+|16: 0-1 d,#2483c5, 2-2 d,#93a1a1, 3-32 #073642,#93a1a1, 33-40 #2d3502,#93a1a1, 41-99 #073642,#93a1a1,
|17: 0-0 d,#839496, 1-3 d,#002b36, 4-6 #586e75,#002b36, 7-92 #839496,#002b36, 93-93 #839496,#657b83, 94-99 #01090b,#657b83,
|18: 0-0 d,#eee8d5, 1-3 d,#002b36, 4-6 #586e75,#002b36, 7-99 #839496,#002b36,
|19: 0-0 d,#eee8d5, 1-3 d,#002b36, 4-6 #586e75,#002b36, 7-99 #839496,#002b36,
@@ -707,7 +707,7 @@
== style step-back grid=100x31
|0: 0-99 #a7a7a7,#3c3c3c,
|1: 0-1 d,#458276, 2-2 d,#1e1e1e, 3-99 #ffffff,#1e1e1e,
-|2: 0-1 d,#1c608e, 2-2 d,#3c3c3c, 3-99 #a7a7a7,#3c3c3c,
+|2: 0-1 d,#1c608e, 2-2 d,#3c3c3c, 3-32 #a7a7a7,#3c3c3c, 33-38 #68aee9,#3c3c3c, 39-99 #a7a7a7,#3c3c3c,
|3: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|4: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|5: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
@@ -721,7 +721,7 @@
|13: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|14: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|15: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
-|16: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-99 #ffffff,#1e1e1e,
+|16: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-32 #ffffff,#1e1e1e, 33-40 #cbe5fc,#1e1e1e, 41-99 #ffffff,#1e1e1e,
|17: 0-0 d,#424242, 1-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-92 #d4d4d4,#1e1e1e, 93-93 #d4d4d4,#3c3c3c, 94-99 #a7a7a7,#3c3c3c,
|18: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|19: 0-3 d,#1e1e1e, 4-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
@@ -768,7 +768,7 @@
|29: 27 line 27
|30: 28 line 28
== style picked grid=100x31
-|2: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-99 #ffffff,#1e1e1e,
+|2: 0-1 d,#007fd4, 2-2 d,#1e1e1e, 3-32 #ffffff,#1e1e1e, 33-38 #cbe5fc,#1e1e1e, 39-99 #ffffff,#1e1e1e,
|3: 0-0 d,#424242, 1-1 d,#1e1e1e, 2-4 d,#1e1e1e,b 5-6 #c6c6c6,#1e1e1e,b 7-99 #d4d4d4,#1e1e1e,
|7: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,
|8: 0-0 d,#424242, 1-4 d,#1e1e1e, 5-6 #858585,#1e1e1e, 7-99 #d4d4d4,#1e1e1e,