diff --git a/CHANGELOG.md b/CHANGELOG.md index 358f468..e83d68b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to `markdown-tui-explorer` are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added — Gaia Aether theme + +A ninth built-in palette: Gruvbox warmth (cream `#f2e5bc` text, moss and +amber hues) on a blue-grey `#181c22` surface, with a dusty-mauve accent. +Select it in the settings modal under **Theme** or with +`theme = "gaia_aether"` in `config.toml`. + +Three slots deliberately depart from the source palette, because its literal +values fail the contrast audit in `src/theme/mod.rs::tests`: + +- `accent.primary` uses the source magenta `#c98fa0` rather than the source + accent `#a0607a`. No foreground colour reaches WCAG AA (4.5:1) on + `#a0607a` — its ceiling is 3.76:1 — so it cannot carry the `on_accent` + text drawn by the status bar and tab bar. `#a0607a` remains available as + `accent.alt`. +- `state.selection_bg` is a lightened `#503a4a`. The source selection + `#261a28` sits 0.16 luminance units from the background surface, below the + `MIN_SELECTION_DELTA` of 3.0, so a selection highlight would be invisible. +- `state.focus` is the magenta accent, 6.45:1 against the base surface. + +The existing audit tests cover the new variant automatically, so the palette +is pinned to the same AA floor as every other theme. + ## [1.35.1] — 2026-08-31 ### Fixed — Mermaid quoted and delimiter-containing labels (#40) diff --git a/README.md b/README.md index 49d9d59..ca7ca30 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ a 500-file cap with a truncation footer. ![Global search modal](https://raw.githubusercontent.com/leboiko/markdown-reader/master/docs/screenshots/global-search.png) -Eight themes ship built-in, including light variants. Settings modal +Nine themes ship built-in, including light variants. Settings modal reached via `c`. ![GitHub Light theme with settings open](https://raw.githubusercontent.com/leboiko/markdown-reader/master/docs/screenshots/settings-github-light.png) @@ -166,8 +166,9 @@ drawn on the left of the viewer content when enabled. The modal supports column-boundary panning (`h`/`l` snap to the next column, `H`/`L` half-page pan), mouse scroll wheel (plain for rows, `Shift`+wheel for columns), and click-outside-to-close. -- **Themes** — eight built-in palettes (Default, Dracula, Solarized Dark, - Solarized Light, Nord, Gruvbox Dark, Gruvbox Light, GitHub Light). +- **Themes** — nine built-in palettes (Default, Dracula, Solarized Dark, + Solarized Light, Nord, Gruvbox Dark, Gruvbox Light, GitHub Light, + Gaia Aether). Switch live from the settings modal; every open document re-renders with the new colors. - **Session resume** — per-project: the last open tabs, active tab, and @@ -710,7 +711,7 @@ it past the viewport and press `Enter` again for the next. ## Themes -Eight built-in themes (five dark, three light), switchable live from +Nine built-in themes (six dark, three light), switchable live from the settings modal (`c`): - **Default** — balanced palette that works on dark terminals. @@ -721,6 +722,8 @@ the settings modal (`c`): - **Gruvbox Dark** — warm retro groove. - **Gruvbox Light** — warm retro light variant. - **GitHub Light** — bright palette for light terminals. +- **Gaia Aether** — Gruvbox warmth (cream text, moss and amber hues) on a + blue-grey surface, with a dusty-mauve accent. Theme changes re-render every open tab immediately, so switching feels instantaneous. The choice is persisted and restored on next launch. @@ -740,7 +743,7 @@ app starts with defaults rather than refusing to launch. Fields: ```toml -theme = "dracula" # default | dracula | solarized_dark | solarized_light | nord | gruvbox_dark | gruvbox_light | github_light +theme = "dracula" # default | dracula | solarized_dark | solarized_light | nord | gruvbox_dark | gruvbox_light | github_light | gaia_aether show_line_numbers = true show_file_tree = true # true | false; false starts hidden (H still toggles) tree_position = "left" # left | right diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 0a9b5aa..788bc4c 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -50,6 +50,7 @@ pub enum Theme { GruvboxDark, GruvboxLight, GithubLight, + GaiaAether, } impl Theme { @@ -63,6 +64,7 @@ impl Theme { Theme::GruvboxDark, Theme::GruvboxLight, Theme::GithubLight, + Theme::GaiaAether, ]; /// Human-readable display name for the theme. @@ -76,6 +78,7 @@ impl Theme { Theme::GruvboxDark => "Gruvbox Dark", Theme::GruvboxLight => "Gruvbox Light", Theme::GithubLight => "GitHub Light", + Theme::GaiaAether => "Gaia Aether", } } @@ -95,6 +98,7 @@ impl Theme { Theme::Default | Theme::SolarizedDark | Theme::Nord => "base16-ocean.dark", Theme::Dracula | Theme::GruvboxDark => "base16-eighties.dark", Theme::SolarizedLight | Theme::GruvboxLight | Theme::GithubLight => "InspiredGitHub", + Theme::GaiaAether => "base16-eighties.dark", } } } diff --git a/src/theme/themes.rs b/src/theme/themes.rs index 722701b..bcd3f52 100644 --- a/src/theme/themes.rs +++ b/src/theme/themes.rs @@ -635,3 +635,102 @@ pub(super) fn github_light() -> Tokens { }, } } + +/// Gaia Aether — the dark Gruvbox/terracotta palette used across the author's +/// desktop theme ("AETHER / G.A.I.A v2": Gruvbox, terracotta, and dusty mauve). +/// +/// Slots carry the source-palette names from that theme's `colors.toml`: +/// `background` `#181c22`, `lighter_background` `#1e2228`, `foreground` +/// `#f2e5bc`, `dark_foreground` `#8a7e70`, `accent` `#a0607a`, `selection` +/// `#261a28`, plus the Gruvbox-derived hue slots. +/// +/// Three slots deviate from the source palette because its literal values +/// fail this crate's own contrast audit: +/// +/// * `accent.primary` is the theme's dusty-mauve/tan `magenta` `#c98fa0` +/// rather than `accent` `#a0607a`. No foreground reaches WCAG AA (4.5:1) +/// on `#a0607a` — its ceiling is 3.76:1 — so it cannot carry the +/// `on_accent` text drawn by the status bar and tab bar. `#a0607a` stays +/// in the palette as `accent.alt`. +/// * `state.selection_bg` `#503a4a` is a lightened `selection` — the +/// source `#261a28` sits only 0.16 luminance units from `background`, +/// below the `MIN_SELECTION_DELTA` of 3.0, and would render invisible. +/// * `state.focus` is the magenta accent, 6.45:1 on `background`. +pub(super) fn gaia_aether() -> Tokens { + // AETHER / G.A.I.A v2 base surfaces and reading text. + let bg = Color::Rgb(24, 28, 34); // background #181c22 + let raised = Color::Rgb(30, 34, 40); // lighter_background #1e2228 + let brown = Color::Rgb(60, 56, 54); // brown #3c3836 + let fg = Color::Rgb(242, 229, 188); // foreground #f2e5bc + let gray = Color::Rgb(168, 153, 132); // gruvbox fg4 #a89984 + let dark_fg = Color::Rgb(138, 126, 112); // dark_foreground #8a7e70 + // Hue slots, Gruvbox-derived in the source theme. + let orange = Color::Rgb(208, 128, 80); // orange #d08050 + let yellow = Color::Rgb(184, 151, 90); // yellow #b8975a + let bright_yellow = Color::Rgb(212, 176, 122); // bright_yellow #d4b07a + let green = Color::Rgb(122, 158, 126); // green #7a9e7e + let cyan = Color::Rgb(131, 165, 152); // cyan #83a598 + let mauve = Color::Rgb(201, 143, 160); // magenta #c98fa0 + let deep_mauve = Color::Rgb(160, 96, 122); // accent #a0607a + let selection = Color::Rgb(80, 58, 74); // selection, lightened #261a28 + Tokens { + surface: Surface { + base: bg, + raised, + border: brown, + }, + text: Text { + primary: fg, + muted: gray, + // Dark base on the bright mauve accent: 6.45:1. + on_accent: bg, + title: fg, + }, + state: State { + selection_bg: selection, + selection_fg: fg, + focus: mauve, + search_bg: bright_yellow, + current_match_bg: orange, + // Dark base reads 8.38:1 on bright_yellow and 5.61:1 on orange. + match_fg: bg, + }, + accent: Accent { + primary: mauve, + alt: deep_mauve, + link: cyan, + }, + syntax: Syntax { + inline_code: green, + code_fg: fg, + code_border: brown, + }, + heading: Heading { + h1: green, + h2: cyan, + h3: mauve, + other: fg, + }, + status: Status { + // Darker than `raised`: the source theme's `dark_background`. + bg: Color::Rgb(22, 26, 31), // #161a1f + fg: gray, + help_bg: bg, + gutter: dark_fg, + }, + list: List { + marker: bright_yellow, + task_marker: green, + block_quote_fg: gray, + block_quote_border: dark_fg, + }, + table: Table { + header: cyan, + border: brown, + }, + git: Git { + new: green, + modified: yellow, + }, + } +} diff --git a/src/theme/tokens.rs b/src/theme/tokens.rs index 9ac441e..65304fc 100644 --- a/src/theme/tokens.rs +++ b/src/theme/tokens.rs @@ -140,6 +140,7 @@ impl Tokens { Theme::GruvboxDark => super::themes::gruvbox_dark(), Theme::GruvboxLight => super::themes::gruvbox_light(), Theme::GithubLight => super::themes::github_light(), + Theme::GaiaAether => super::themes::gaia_aether(), } } }