makeover 3.6.0

Shared theme loading for the make-family apps: TOML theme files parsed into intent-based color tokens, with perceptual derivations and WCAG contrast.
Documentation
//! Shared theme loading + intent resolution for TOML-based theme files.
//!
//! Used by GoingsOn, Balanced Breakfast (Tauri apps), audiofiles (egui), and the
//! MNW web server. Themes are authored by **intent** ("human design"): colors are
//! declared by role (surface / content / action / status / line / category), not
//! by hue. This crate is the single place that resolves an authored theme into a
//! full set of intent tokens, including the derived interactive states
//! (hover/row tones/contrast), and emits them as CSS
//! variables or RGB tuples. No app recomputes them itself.
//!
//! Theme file shape:
//! ```text
//! [meta]
//! name = "Nord"
//! variant = "dark"          # or "light"
//!
//! [surface]                 # container backgrounds by role/elevation
//! page = "#2e3440"; raised = "#3b4252"; sunken = "#434c5e"; overlay = "#3b4252"
//!
//! [content]                 # the ink. Its emphasis steps are derived, not authored:
//! primary = "#d8dee9"       # `content-secondary` and `content-muted` are tonal
//!                           # steps of this toward `surface.page`. See `Emphasis`.
//!
//! [action]                  # interactive / brand color
//! primary = "#81a1c1"
//!
//! [status]                  # state semantics
//! danger = "#bf616a"; success = "#a3be8c"; warning = "#ebcb8b"; info = "#88c0d0"
//!
//! [line]
//! border = "#4c566a"
//!
//! [category]                # distinct decorative colors for tags/badges/charts
//! one = "#bf616a"; two = "#a3be8c"; three = "#81a1c1"
//! four = "#ebcb8b"; five = "#b48ead"; six = "#88c0d0"
//! ```

// Color-space math: single-letter channel names (r/g/b/l/m/s) and the published
// high-precision OKLab/sRGB matrix constants are the domain vocabulary here.
#![allow(clippy::many_single_char_names, clippy::unreadable_literal)]

use serde::Serialize;
use std::collections::HashMap;

mod ansi;
mod color;
mod dirs;
mod emphasis;
mod font;
mod intent;
mod load;
mod selection;
mod sheet;
mod typography;

#[cfg(test)]
pub(crate) mod fixture;

// Every public path this crate has ever offered is a root path. The named
// re-exports below are that promise: a module is an internal seam, never an
// address a caller has to learn.
pub use ansi::{
    ANSI_16, ANSI_240, ANSI_240_OFFSET, ANSI_256, DISTINCT, GRAY_RAMP_LEN, GRAY_RAMP_START,
    ansi_intent, gray_ramp, quantize, quantize_against,
};
pub use color::{Oklab, Rgb, darken, lighten, mix, readable_on, wcag_contrast};
pub use dirs::{ThemeDirs, bundled_themes_dir, embedded_themes, find_theme_path};
pub use emphasis::{Emphasis, STEP_FLOOR, emphasized, tonal};
pub use font::{FontFace, FontOverride, FontSlot, Typography};
pub use intent::{BASE_INTENTS, SemanticTokens, intent_css_declarations, intent_css_vars, resolve};
pub use load::{
    ThemePreview, delete_theme, derive_tonal_steps, export_theme, extract_colors, import_theme,
    list_themes_from_dirs, load_semantic, load_theme, load_theme_preview, parse_meta,
    parse_theme_str, validate_theme_id,
};
pub use selection::{
    ContrastTier, FOLLOW, ThemeDefaults, ThemeOption, ThemeSelection, Variant, order_theme_options,
    theme_options,
};
pub use sheet::{THEME_ATTRIBUTE, all_themes_css, keyed_intent_css_vars};
pub use typography::{
    FONT_MONO, FONT_SANS, HOUSE_MONO_FAMILY, HOUSE_SANS_FAMILY, HOUSE_WEIGHT_RANGE,
    WEBFONT_MONO_FILE, WEBFONT_SANS_FILE, font_face_css, typography_css_declarations,
    typography_css_vars,
};

/// The color sections an authored theme may declare.
pub const COLOR_SECTIONS: &[&str] = &["surface", "content", "action", "status", "line", "category"];

/// Theme metadata parsed from the `[meta]` section.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ThemeMeta {
    pub id: String,
    pub name: String,
    pub variant: String,
    pub is_custom: bool,
}

/// A loaded theme: metadata plus the authored colors, flattened to dotted keys
/// (e.g. `"surface.page"`, `"status.danger"`, `"category.one"`).
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ThemeColors {
    pub meta: ThemeMeta,
    pub colors: HashMap<String, String>,
}

#[cfg(test)]
mod tests {
    // Every name a caller is told to use is exported from the crate root. The
    // modules under it are internal seams, so an item that moves between them
    // still has to answer here. This import stops compiling the moment one is
    // reachable only through its module, which on a published crate is a
    // breaking release against consumers this repo cannot see.
    #[allow(unused_imports)]
    use crate::{
        ANSI_16, ANSI_240, ANSI_240_OFFSET, ANSI_256, BASE_INTENTS, COLOR_SECTIONS, ContrastTier,
        DISTINCT, Emphasis, FOLLOW, FONT_MONO, FONT_SANS, FontFace, FontOverride, FontSlot,
        GRAY_RAMP_LEN, GRAY_RAMP_START, HOUSE_MONO_FAMILY, HOUSE_SANS_FAMILY, HOUSE_WEIGHT_RANGE,
        Oklab, Rgb, STEP_FLOOR, SemanticTokens, THEME_ATTRIBUTE, ThemeColors, ThemeDefaults,
        ThemeDirs, ThemeMeta, ThemeOption, ThemePreview, ThemeSelection, Typography, Variant,
        WEBFONT_MONO_FILE, WEBFONT_SANS_FILE, all_themes_css, ansi_intent, bundled_themes_dir,
        darken, delete_theme, derive_tonal_steps, embedded_themes, emphasized, export_theme,
        extract_colors, find_theme_path, font_face_css, gray_ramp, import_theme,
        intent_css_declarations, intent_css_vars, keyed_intent_css_vars, lighten,
        list_themes_from_dirs, load_semantic, load_theme, load_theme_preview, mix,
        order_theme_options, parse_meta, parse_theme_str, quantize, quantize_against, readable_on,
        resolve, theme_options, tonal, typography_css_declarations, typography_css_vars,
        validate_theme_id, wcag_contrast,
    };
}