diff --git a/Sources/TUIkit/Modifiers/BorderModifier.swift b/Sources/TUIkit/Modifiers/BorderModifier.swift index c362306..53ae22f 100644 --- a/Sources/TUIkit/Modifiers/BorderModifier.swift +++ b/Sources/TUIkit/Modifiers/BorderModifier.swift @@ -76,7 +76,7 @@ extension BorderedView: Renderable { /// Renders with half-block characters for block appearance. private func renderBlockStyle(buffer: FrameBuffer, innerWidth: Int, palette: any Palette) -> FrameBuffer { - let containerBg = palette.containerBodyBackground + let containerBg = palette.blockSurfaceBackground var lines: [String] = [] lines.append(BorderRenderer.blockTopBorder(innerWidth: innerWidth, color: containerBg)) diff --git a/Sources/TUIkit/Styling/Color.swift b/Sources/TUIkit/Styling/Color.swift index 8a31dd1..bfd6fb2 100644 --- a/Sources/TUIkit/Styling/Color.swift +++ b/Sources/TUIkit/Styling/Color.swift @@ -123,24 +123,24 @@ public struct Color: Sendable, Equatable { /// Text("Hello").foregroundColor(.palette.accent) /// ``` public enum Semantic { - // Background colors + // Background colors (Palette) public static let background = Color(value: .semantic(.background)) - public static let containerBodyBackground = Color(value: .semantic(.containerBodyBackground)) - public static let containerCapBackground = Color(value: .semantic(.containerCapBackground)) - public static let buttonBackground = Color(value: .semantic(.buttonBackground)) public static let statusBarBackground = Color(value: .semantic(.statusBarBackground)) public static let appHeaderBackground = Color(value: .semantic(.appHeaderBackground)) public static let overlayBackground = Color(value: .semantic(.overlayBackground)) + // Background colors (BlockPalette) + public static let surfaceBackground = Color(value: .semantic(.surfaceBackground)) + public static let surfaceHeaderBackground = Color(value: .semantic(.surfaceHeaderBackground)) + public static let elevatedBackground = Color(value: .semantic(.elevatedBackground)) + // Foreground colors public static let foreground = Color(value: .semantic(.foreground)) public static let foregroundSecondary = Color(value: .semantic(.foregroundSecondary)) public static let foregroundTertiary = Color(value: .semantic(.foregroundTertiary)) - public static let foregroundPlaceholder = Color(value: .semantic(.foregroundPlaceholder)) // Accent colors public static let accent = Color(value: .semantic(.accent)) - public static let accentSecondary = Color(value: .semantic(.accentSecondary)) // Status colors public static let success = Color(value: .semantic(.success)) @@ -294,14 +294,69 @@ public struct Color: Sendable, Equatable { adjusted(by: -amount) } + // MARK: - RGB Conversion + + /// The RGB components of this color. + /// + /// Converts any color type to its RGB representation: + /// - `.rgb` — returned directly + /// - `.standard` / `.bright` — mapped to xterm standard RGB values + /// - `.palette256` — mapped to xterm 256-color palette RGB values + /// - `.semantic` — returns nil (must be resolved first via ``resolve(with:)``) + public var rgbComponents: (red: UInt8, green: UInt8, blue: UInt8)? { + switch value { + case .rgb(let red, let green, let blue): + return (red, green, blue) + case .standard(let ansi): + return ansi.rgbValues + case .bright(let ansi): + return ansi.brightRGBValues + case .palette256(let index): + return Self.palette256ToRGB(index) + case .semantic: + return nil + } + } + + /// Converts a 256-color palette index to RGB values. + /// + /// - Indices 0–7: standard ANSI colors + /// - Indices 8–15: bright ANSI colors + /// - Indices 16–231: 6×6×6 color cube + /// - Indices 232–255: grayscale ramp + private static func palette256ToRGB(_ index: UInt8) -> (red: UInt8, green: UInt8, blue: UInt8) { + switch index { + case 0...7: + guard let ansi = ANSIColor(rawValue: index) else { return (0, 0, 0) } + return ansi.rgbValues + case 8...15: + guard let ansi = ANSIColor(rawValue: index - 8) else { return (0, 0, 0) } + return ansi.brightRGBValues + case 16...231: + // 6×6×6 color cube: index = 16 + 36*r + 6*g + b (each 0–5) + let cubeIndex = index - 16 + let cubeRed = cubeIndex / 36 + let cubeGreen = (cubeIndex % 36) / 6 + let cubeBlue = cubeIndex % 6 + let channelMap: [UInt8] = [0, 95, 135, 175, 215, 255] + return (channelMap[Int(cubeRed)], channelMap[Int(cubeGreen)], channelMap[Int(cubeBlue)]) + default: + // Grayscale ramp: 232–255 → 8, 18, 28, ..., 238 + let gray = UInt8(8 + Int(index - 232) * 10) + return (gray, gray, gray) + } + } + /// Adjusts brightness by a signed amount. /// - /// Positive values lighten, negative values darken. + /// Positive values lighten, negative values darken. Works with all color types + /// (ANSI, 256-palette, RGB) by converting to RGB first. The result is always + /// an RGB color. /// /// - Parameter amount: The adjustment amount (-1 to 1). - /// - Returns: The adjusted color, or self if not an RGB color. + /// - Returns: The adjusted color as RGB, or self if semantic (unresolved). private func adjusted(by amount: Double) -> Self { - guard case .rgb(let red, let green, let blue) = value else { + guard let (red, green, blue) = rgbComponents else { return self } @@ -316,12 +371,13 @@ public struct Color: Sendable, Equatable { /// Returns a color with adjusted opacity (simulated via color mixing). /// /// Since terminals don't support true transparency, this mixes - /// the color with black to simulate opacity. + /// the color with black to simulate opacity. Works with all color types + /// by converting to RGB first. /// /// - Parameter opacity: The opacity (0-1). - /// - Returns: A color simulating the given opacity. + /// - Returns: A color simulating the given opacity, or self if semantic. public func opacity(_ opacity: Double) -> Self { - guard case .rgb(let red, let green, let blue) = value else { + guard let (red, green, blue) = rgbComponents else { return self } @@ -366,4 +422,36 @@ enum ANSIColor: UInt8, Sendable { var brightBackgroundCode: UInt8 { 100 + rawValue } + + // MARK: - xterm Standard RGB Values + + /// The standard RGB values for this ANSI color (xterm defaults). + var rgbValues: (red: UInt8, green: UInt8, blue: UInt8) { + switch self { + case .black: return (0, 0, 0) + case .red: return (205, 0, 0) + case .green: return (0, 205, 0) + case .yellow: return (205, 205, 0) + case .blue: return (0, 0, 238) + case .magenta: return (205, 0, 205) + case .cyan: return (0, 205, 205) + case .white: return (229, 229, 229) + case .default: return (229, 229, 229) + } + } + + /// The bright RGB values for this ANSI color (xterm defaults). + var brightRGBValues: (red: UInt8, green: UInt8, blue: UInt8) { + switch self { + case .black: return (127, 127, 127) + case .red: return (255, 0, 0) + case .green: return (0, 255, 0) + case .yellow: return (255, 255, 0) + case .blue: return (92, 92, 255) + case .magenta: return (255, 0, 255) + case .cyan: return (0, 255, 255) + case .white: return (255, 255, 255) + case .default: return (255, 255, 255) + } + } } diff --git a/Sources/TUIkit/Styling/Palettes/AmberPalette.swift b/Sources/TUIkit/Styling/Palettes/AmberPalette.swift index 3ad30c2..7221abc 100644 --- a/Sources/TUIkit/Styling/Palettes/AmberPalette.swift +++ b/Sources/TUIkit/Styling/Palettes/AmberPalette.swift @@ -9,24 +9,20 @@ /// /// Inspired by terminals like the IBM 3278 and Wyse 50. /// Uses a dark background with subtle amber/orange tint. -public struct AmberPalette: Palette { +public struct AmberPalette: BlockPalette { public let id = "amber" public let name = "Amber" - // Background hierarchy - public let background = Color.hex(0x0A0706) // App background (darkest) - public let containerBodyBackground = Color.hex(0x251710) // Container content background - public let containerCapBackground = Color.hex(0x1E110E) // Container header/footer background + // Background + public let background = Color.hex(0x0A0706) // Amber text hierarchy (matching Spotnik) public let foreground = Color.hex(0xFFAA00) // Bright amber - primary text public let foregroundSecondary = Color.hex(0xCC8800) // Medium amber - secondary text public let foregroundTertiary = Color.hex(0x8F6600) // Dim amber - tertiary/muted text - public let foregroundPlaceholder = Color.hex(0x664D00) // Faint amber - placeholder text - // Accent colors + // Accent public let accent = Color.hex(0xFFCC33) // Lighter amber for highlights - public let accentSecondary = Color.hex(0xCC9900) // Darker accent // Semantic colors (stay in amber family) public let success = Color.hex(0xFFCC00) @@ -39,16 +35,15 @@ public struct AmberPalette: Palette { // Additional backgrounds public let statusBarBackground = Color.hex(0x191613) - public let appHeaderBackground = Color.hex(0x1E110E) // Same as cap - public let overlayBackground = Color.hex(0x0A0706) // Dimming overlay - public var buttonBackground: Color { Color.hex(0x3A2A1D) } // Lighter amber for buttons + public let appHeaderBackground = Color.hex(0x1E110E) + public let overlayBackground = Color.hex(0x0A0706) public init() {} } // MARK: - Convenience Accessors -extension Palette where Self == AmberPalette { +extension BlockPalette where Self == AmberPalette { /// Amber terminal palette. public static var amber: AmberPalette { AmberPalette() } } diff --git a/Sources/TUIkit/Styling/Palettes/BluePalette.swift b/Sources/TUIkit/Styling/Palettes/BluePalette.swift index 1e7a7ed..4f4a74a 100644 --- a/Sources/TUIkit/Styling/Palettes/BluePalette.swift +++ b/Sources/TUIkit/Styling/Palettes/BluePalette.swift @@ -9,24 +9,20 @@ /// Inspired by vintage vacuum fluorescent displays (VFDs) found in /// audio equipment, cash registers, and instrument panels. Uses the /// characteristic bright cyan-blue glow on a near-black background. -public struct BluePalette: Palette { +public struct BluePalette: BlockPalette { public let id = "blue" public let name = "Blue" - // Background hierarchy - public let background = Color.hex(0x060708) // App background (darkest) - public let containerBodyBackground = Color.hex(0x0E1825) // Container content background - public let containerCapBackground = Color.hex(0x0A121C) // Container header/footer background + // Background + public let background = Color.hex(0x060708) // Blue text hierarchy public let foreground = Color.hex(0x00AAFF) // Bright VFD blue - primary text public let foregroundSecondary = Color.hex(0x0088CC) // Medium blue - secondary text public let foregroundTertiary = Color.hex(0x006699) // Dim blue - tertiary/muted text - public let foregroundPlaceholder = Color.hex(0x004D73) // Faint blue - placeholder text - // Accent colors + // Accent public let accent = Color.hex(0x33BBFF) // Lighter blue for highlights - public let accentSecondary = Color.hex(0x0099DD) // Darker accent // Semantic colors (stay in blue family) public let success = Color.hex(0x33CCFF) // Cyan-blue @@ -38,17 +34,16 @@ public struct BluePalette: Palette { public let border = Color.hex(0x2D4A5A) // Subtle blue border // Additional backgrounds - public let statusBarBackground = Color.hex(0x0F1822) // Dark blue for status bar - public let appHeaderBackground = Color.hex(0x0A121C) // Same as cap - public let overlayBackground = Color.hex(0x060708) // Dimming overlay - public var buttonBackground: Color { Color.hex(0x14304A) } // Lighter blue for buttons + public let statusBarBackground = Color.hex(0x0F1822) + public let appHeaderBackground = Color.hex(0x0A121C) + public let overlayBackground = Color.hex(0x060708) public init() {} } // MARK: - Convenience Accessors -extension Palette where Self == BluePalette { +extension BlockPalette where Self == BluePalette { /// Blue VFD terminal palette. public static var blue: BluePalette { BluePalette() } } diff --git a/Sources/TUIkit/Styling/Palettes/GreenPalette.swift b/Sources/TUIkit/Styling/Palettes/GreenPalette.swift index 2da5fb8..e15abe9 100644 --- a/Sources/TUIkit/Styling/Palettes/GreenPalette.swift +++ b/Sources/TUIkit/Styling/Palettes/GreenPalette.swift @@ -9,24 +9,20 @@ /// /// Inspired by early CRT monitors like the IBM 5151 and Apple II. /// Uses a dark background with subtle green tint. -public struct GreenPalette: Palette { +public struct GreenPalette: BlockPalette { public let id = "green" public let name = "Green" - // Background hierarchy - public let background = Color.hex(0x060A07) // App background (darkest) - public let containerBodyBackground = Color.hex(0x0E271C) // Container content background - public let containerCapBackground = Color.hex(0x0A1B13) // Container header/footer background + // Background + public let background = Color.hex(0x060A07) // Green text hierarchy public let foreground = Color.hex(0x33FF33) // Bright green - primary text public let foregroundSecondary = Color.hex(0x27C227) // Medium green - secondary text public let foregroundTertiary = Color.hex(0x1F8F1F) // Dim green - tertiary/muted text - public let foregroundPlaceholder = Color.hex(0x165A16) // Faint green - placeholder text - // Accent colors + // Accent public let accent = Color.hex(0x66FF66) // Lighter green for highlights - public let accentSecondary = Color.hex(0x00CC00) // Darker accent // Semantic colors (stay in green family) public let success = Color.hex(0x33FF33) @@ -38,17 +34,16 @@ public struct GreenPalette: Palette { public let border = Color.hex(0x2D5A2D) // Subtle green border // Additional backgrounds - public let statusBarBackground = Color.hex(0x0F2215) // Dark green for status bar - public let appHeaderBackground = Color.hex(0x0A1B13) // Same as cap - public let overlayBackground = Color.hex(0x060A07) // Dimming overlay - public var buttonBackground: Color { Color.hex(0x145523) } // Lighter green for buttons + public let statusBarBackground = Color.hex(0x0F2215) + public let appHeaderBackground = Color.hex(0x0A1B13) + public let overlayBackground = Color.hex(0x060A07) public init() {} } // MARK: - Convenience Accessors -extension Palette where Self == GreenPalette { +extension BlockPalette where Self == GreenPalette { /// The default palette (green). public static var `default`: GreenPalette { GreenPalette() } /// Green terminal palette. diff --git a/Sources/TUIkit/Styling/Palettes/NCursesPalette.swift b/Sources/TUIkit/Styling/Palettes/NCursesPalette.swift deleted file mode 100644 index fddcba3..0000000 --- a/Sources/TUIkit/Styling/Palettes/NCursesPalette.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// NCursesPalette.swift -// TUIkit -// -// Classic ncurses-style palette. -// - -/// Classic ncurses-style palette. -/// -/// Traditional terminal colors as used in ncurses applications -/// like htop, mc (Midnight Commander), and vim. -public struct NCursesPalette: Palette { - public let id = "ncurses" - public let name = "ncurses" - - // Standard terminal black background - public let background = Color.black - public let containerBodyBackground = Color.blue - public let containerCapBackground = Color.brightBlack - public let foreground = Color.white - public let foregroundSecondary = Color.brightWhite - public let foregroundTertiary = Color.brightBlack - public let foregroundPlaceholder = Color.brightBlack - public let accent = Color.cyan - public let accentSecondary = Color.brightCyan - public let success = Color.green - public let warning = Color.yellow - public let error = Color.red - public let info = Color.cyan - public let border = Color.white - public let statusBarBackground = Color.blue - public let appHeaderBackground = Color.brightBlack - public let overlayBackground = Color.black - public var buttonBackground: Color { Color.brightBlue } - - public init() {} -} - -// MARK: - Convenience Accessors - -extension Palette where Self == NCursesPalette { - /// Classic ncurses palette. - public static var ncurses: NCursesPalette { NCursesPalette() } -} diff --git a/Sources/TUIkit/Styling/Palettes/RedPalette.swift b/Sources/TUIkit/Styling/Palettes/RedPalette.swift index 59b775a..d0458a4 100644 --- a/Sources/TUIkit/Styling/Palettes/RedPalette.swift +++ b/Sources/TUIkit/Styling/Palettes/RedPalette.swift @@ -9,24 +9,20 @@ /// /// Less common but used in some military and specialized applications. /// Night-vision friendly with reduced eye strain in dark environments. -public struct RedPalette: Palette { +public struct RedPalette: BlockPalette { public let id = "red" public let name = "Red" - // Background hierarchy - public let background = Color.hex(0x0A0606) // App background (darkest) - public let containerBodyBackground = Color.hex(0x281112) // Container content background - public let containerCapBackground = Color.hex(0x1E0F10) // Container header/footer background + // Background + public let background = Color.hex(0x0A0606) // Red text hierarchy public let foreground = Color.hex(0xFF4444) // Bright red - primary text public let foregroundSecondary = Color.hex(0xCC3333) // Medium red - secondary text public let foregroundTertiary = Color.hex(0x8F2222) // Dim red - tertiary/muted text - public let foregroundPlaceholder = Color.hex(0x661616) // Faint red - placeholder text - // Accent colors + // Accent public let accent = Color.hex(0xFF6666) // Lighter red for highlights - public let accentSecondary = Color.hex(0xCC4444) // Darker accent // Semantic colors (stay in red family) public let success = Color.hex(0xFF8080) // Light red (success in red theme) @@ -39,16 +35,15 @@ public struct RedPalette: Palette { // Additional backgrounds public let statusBarBackground = Color.hex(0x191313) - public let appHeaderBackground = Color.hex(0x1E0F10) // Same as cap - public let overlayBackground = Color.hex(0x0A0606) // Dimming overlay - public var buttonBackground: Color { Color.hex(0x3A1F22) } // Lighter red for buttons + public let appHeaderBackground = Color.hex(0x1E0F10) + public let overlayBackground = Color.hex(0x0A0606) public init() {} } // MARK: - Convenience Accessors -extension Palette where Self == RedPalette { +extension BlockPalette where Self == RedPalette { /// Red terminal palette. public static var red: RedPalette { RedPalette() } } diff --git a/Sources/TUIkit/Styling/Palettes/VioletPalette.swift b/Sources/TUIkit/Styling/Palettes/VioletPalette.swift index fd3192a..58be657 100644 --- a/Sources/TUIkit/Styling/Palettes/VioletPalette.swift +++ b/Sources/TUIkit/Styling/Palettes/VioletPalette.swift @@ -9,27 +9,23 @@ /// Inspired by retro computing aesthetics and sci-fi terminal displays. /// All colors are generated algorithmically from a single base hue (270°) /// using HSL transformations. -public struct VioletPalette: Palette { +public struct VioletPalette: BlockPalette { public let id = "violet" public let name = "Violet" /// The base hue used to generate all palette colors. private static let baseHue: Double = 270 - // Background hierarchy + // Background public let background: Color - public let containerBodyBackground: Color - public let containerCapBackground: Color // Violet text hierarchy public let foreground: Color public let foregroundSecondary: Color public let foregroundTertiary: Color - public let foregroundPlaceholder: Color - // Accent colors + // Accent public let accent: Color - public let accentSecondary: Color // Semantic colors public let success: Color @@ -44,25 +40,20 @@ public struct VioletPalette: Palette { public let statusBarBackground: Color public let appHeaderBackground: Color public let overlayBackground: Color - public let buttonBackground: Color public init() { let hue = Self.baseHue - // Backgrounds: very dark, subtly tinted + // Background: very dark, subtly tinted self.background = Color.hsl(hue, 30, 3) - self.containerBodyBackground = Color.hsl(hue, 40, 10) - self.containerCapBackground = Color.hsl(hue, 35, 7) // Foregrounds: bright, saturated text self.foreground = Color.hsl(hue, 80, 70) self.foregroundSecondary = Color.hsl(hue, 70, 55) self.foregroundTertiary = Color.hsl(hue, 60, 40) - self.foregroundPlaceholder = Color.hsl(hue, 50, 28) - // Accents: lighter/brighter variant + // Accent: lighter/brighter variant self.accent = Color.hsl(hue, 85, 78) - self.accentSecondary = Color.hsl(hue, 75, 50) // Semantic: hue-shifted from base self.success = Color.hsl(Self.wrapHue(hue + 120), 70, 65) @@ -75,9 +66,8 @@ public struct VioletPalette: Palette { // Additional backgrounds self.statusBarBackground = Color.hsl(hue, 35, 8) - self.appHeaderBackground = Color.hsl(hue, 35, 7) // Same as cap - self.overlayBackground = Color.hsl(hue, 30, 3) // Same as background - self.buttonBackground = Color.hsl(hue, 45, 15) + self.appHeaderBackground = Color.hsl(hue, 35, 7) + self.overlayBackground = Color.hsl(hue, 30, 3) } /// Wraps a hue value to the 0–360 range. @@ -90,7 +80,7 @@ public struct VioletPalette: Palette { // MARK: - Convenience Accessors -extension Palette where Self == VioletPalette { +extension BlockPalette where Self == VioletPalette { /// Violet terminal palette. public static var violet: VioletPalette { VioletPalette() } } diff --git a/Sources/TUIkit/Styling/Palettes/WhitePalette.swift b/Sources/TUIkit/Styling/Palettes/WhitePalette.swift index 1223484..0f03925 100644 --- a/Sources/TUIkit/Styling/Palettes/WhitePalette.swift +++ b/Sources/TUIkit/Styling/Palettes/WhitePalette.swift @@ -9,24 +9,20 @@ /// /// Inspired by terminals like the DEC VT100 and VT220. /// Uses a dark background with subtle cool/blue tint. -public struct WhitePalette: Palette { +public struct WhitePalette: BlockPalette { public let id = "white" public let name = "White" - // Background hierarchy - public let background = Color.hex(0x06070A) // App background (darkest) - public let containerBodyBackground = Color.hex(0x111A2A) // Container content background - public let containerCapBackground = Color.hex(0x0D131D) // Container header/footer background + // Background + public let background = Color.hex(0x06070A) // White/gray text hierarchy public let foreground = Color.hex(0xE8E8E8) // Bright white - primary text public let foregroundSecondary = Color.hex(0xB0B0B0) // Medium gray - secondary text public let foregroundTertiary = Color.hex(0x787878) // Dim gray - tertiary/muted text - public let foregroundPlaceholder = Color.hex(0x505050) // Faint gray - placeholder text - // Accent colors + // Accent public let accent = Color.hex(0xFFFFFF) // Pure white for highlights - public let accentSecondary = Color.hex(0xC0C0C0) // Light gray accent // Semantic colors (subtle tints) public let success = Color.hex(0xC0FFC0) // Slight green tint @@ -39,16 +35,15 @@ public struct WhitePalette: Palette { // Additional backgrounds public let statusBarBackground = Color.hex(0x131619) - public let appHeaderBackground = Color.hex(0x0D131D) // Same as cap - public let overlayBackground = Color.hex(0x06070A) // Dimming overlay - public var buttonBackground: Color { Color.hex(0x1D2535) } // Lighter gray for buttons + public let appHeaderBackground = Color.hex(0x0D131D) + public let overlayBackground = Color.hex(0x06070A) public init() {} } // MARK: - Convenience Accessors -extension Palette where Self == WhitePalette { +extension BlockPalette where Self == WhitePalette { /// White terminal palette. public static var white: WhitePalette { WhitePalette() } } diff --git a/Sources/TUIkit/Styling/SemanticColor.swift b/Sources/TUIkit/Styling/SemanticColor.swift index ade3370..61759ac 100644 --- a/Sources/TUIkit/Styling/SemanticColor.swift +++ b/Sources/TUIkit/Styling/SemanticColor.swift @@ -18,24 +18,24 @@ /// Text("Hello").foregroundColor(.palette.accent) /// ``` enum SemanticColor: String, Sendable, Equatable { - // Background + // Background (Palette) case background - case containerBodyBackground - case containerCapBackground - case buttonBackground case statusBarBackground case appHeaderBackground case overlayBackground + // Background (BlockPalette) + case surfaceBackground + case surfaceHeaderBackground + case elevatedBackground + // Foreground case foreground case foregroundSecondary case foregroundTertiary - case foregroundPlaceholder // Accent case accent - case accentSecondary // Status case success @@ -48,28 +48,37 @@ enum SemanticColor: String, Sendable, Equatable { /// Resolves this token to a concrete color using the given palette. /// + /// For ``BlockPalette``-specific tokens (`surfaceBackground`, + /// `surfaceHeaderBackground`, `elevatedBackground`), the palette is + /// cast to ``BlockPalette``. If the cast fails, `background` is used + /// as fallback. + /// /// - Parameter palette: The palette to read from. /// - Returns: The concrete ``Color`` from the palette. func resolve(with palette: any Palette) -> Color { switch self { + // Palette properties case .background: palette.background - case .containerBodyBackground: palette.containerBodyBackground - case .containerCapBackground: palette.containerCapBackground - case .buttonBackground: palette.buttonBackground case .statusBarBackground: palette.statusBarBackground case .appHeaderBackground: palette.appHeaderBackground case .overlayBackground: palette.overlayBackground case .foreground: palette.foreground case .foregroundSecondary: palette.foregroundSecondary case .foregroundTertiary: palette.foregroundTertiary - case .foregroundPlaceholder: palette.foregroundPlaceholder case .accent: palette.accent - case .accentSecondary: palette.accentSecondary case .success: palette.success case .warning: palette.warning case .error: palette.error case .info: palette.info case .border: palette.border + + // BlockPalette properties (fallback to background) + case .surfaceBackground: + (palette as? any BlockPalette)?.surfaceBackground ?? palette.background + case .surfaceHeaderBackground: + (palette as? any BlockPalette)?.surfaceHeaderBackground ?? palette.background + case .elevatedBackground: + (palette as? any BlockPalette)?.elevatedBackground ?? palette.background } } } diff --git a/Sources/TUIkit/Styling/Theme.swift b/Sources/TUIkit/Styling/Theme.swift index 10c4a9d..2c81293 100644 --- a/Sources/TUIkit/Styling/Theme.swift +++ b/Sources/TUIkit/Styling/Theme.swift @@ -17,6 +17,9 @@ import Foundation /// /// Conforms to ``Cyclable`` so it can be managed by a ``ThemeManager``. /// +/// For block-appearance-specific backgrounds (surfaces, elevated elements), +/// see ``BlockPalette``. +/// /// # Usage /// /// ```swift @@ -32,19 +35,10 @@ public protocol Palette: Cyclable { /// The app background color (darkest). var background: Color { get } - /// Container body/content background. - var containerBodyBackground: Color { get } - - /// Container header/footer background (the "cap" of a container). - var containerCapBackground: Color { get } - - /// Button background (slightly lighter than containerCapBackground). - var buttonBackground: Color { get } - /// Status bar background. var statusBarBackground: Color { get } - /// App header background (for future use). + /// App header background. var appHeaderBackground: Color { get } /// Dimming overlay background for alerts and dialogs. @@ -61,17 +55,11 @@ public protocol Palette: Cyclable { /// Tertiary text color (even less prominent). var foregroundTertiary: Color { get } - /// Placeholder text color (weakest foreground, e.g. for empty input fields). - var foregroundPlaceholder: Color { get } - // MARK: - Accent Colors /// Primary accent color for interactive elements. var accent: Color { get } - /// Secondary accent color. - var accentSecondary: Color { get } - // MARK: - Semantic Colors /// Color for success states. @@ -97,23 +85,81 @@ public protocol Palette: Cyclable { extension Palette { // MARK: - Background Defaults - public var containerBodyBackground: Color { background } - public var containerCapBackground: Color { background } - public var buttonBackground: Color { containerCapBackground } public var statusBarBackground: Color { background } - public var appHeaderBackground: Color { containerCapBackground } + public var appHeaderBackground: Color { background } public var overlayBackground: Color { background } // MARK: - Foreground Defaults public var foregroundSecondary: Color { foreground } public var foregroundTertiary: Color { foreground } - public var foregroundPlaceholder: Color { foregroundTertiary } +} - // MARK: - Accent Defaults +// MARK: - BlockPalette Protocol - public var accentSecondary: Color { accent } +/// A palette with additional background colors for block-style appearances. +/// +/// Block appearances use solid background fills to visually separate containers, +/// headers, and interactive elements. `BlockPalette` extends ``Palette`` with +/// three surface-level backgrounds that create this visual hierarchy. +/// +/// All three properties provide computed defaults based on ``Palette/background`` +/// using ``Color/lighter(by:)``, so conforming types don't need to define them +/// explicitly unless custom values are desired. +/// +/// # Default Hierarchy +/// +/// ``` +/// background (darkest) +/// └── surfaceHeaderBackground (background.lighter(by: 0.05)) +/// └── surfaceBackground (background.lighter(by: 0.08)) +/// └── elevatedBackground (surfaceHeaderBackground.lighter(by: 0.05)) +/// ``` +public protocol BlockPalette: Palette { + /// Container body/content background. + /// + /// Used for the main content area of containers, menus, and bordered regions + /// in block appearance mode. + var surfaceBackground: Color { get } + /// Container header/footer background. + /// + /// Used for the "cap" area of containers (title bars, footers) and menu + /// headers in block appearance mode. + var surfaceHeaderBackground: Color { get } + + /// Elevated element background (buttons, interactive surfaces). + /// + /// Used for elements that sit visually "above" the surface, such as + /// buttons in block appearance mode. + var elevatedBackground: Color { get } +} + +// MARK: - Default BlockPalette Implementation + +extension BlockPalette { + public var surfaceBackground: Color { background.lighter(by: 0.08) } + public var surfaceHeaderBackground: Color { background.lighter(by: 0.05) } + public var elevatedBackground: Color { surfaceHeaderBackground.lighter(by: 0.05) } +} + +// MARK: - BlockPalette Convenience Accessors + +extension Palette { + /// The surface background if this palette is a ``BlockPalette``, otherwise ``background``. + var blockSurfaceBackground: Color { + (self as? any BlockPalette)?.surfaceBackground ?? background + } + + /// The surface header background if this palette is a ``BlockPalette``, otherwise ``background``. + var blockSurfaceHeaderBackground: Color { + (self as? any BlockPalette)?.surfaceHeaderBackground ?? background + } + + /// The elevated background if this palette is a ``BlockPalette``, otherwise ``background``. + var blockElevatedBackground: Color { + (self as? any BlockPalette)?.elevatedBackground ?? background + } } // MARK: - Palette Environment Key @@ -153,7 +199,7 @@ extension EnvironmentValues { struct PaletteRegistry { /// All available palettes in cycling order. /// - /// Order: Green → Amber → Red → Violet → Blue → White → NCurses + /// Order: Green → Amber → Red → Violet → Blue → White static let all: [any Palette] = [ GreenPalette(), AmberPalette(), @@ -161,7 +207,6 @@ struct PaletteRegistry { VioletPalette(), BluePalette(), WhitePalette(), - NCursesPalette(), ] /// Finds a palette by ID. diff --git a/Sources/TUIkit/TUIkit.docc/Articles/AppearanceAndColors.md b/Sources/TUIkit/TUIkit.docc/Articles/AppearanceAndColors.md index 1c888ca..c4d5626 100644 --- a/Sources/TUIkit/TUIkit.docc/Articles/AppearanceAndColors.md +++ b/Sources/TUIkit/TUIkit.docc/Articles/AppearanceAndColors.md @@ -103,7 +103,7 @@ Use `Color.palette.*` — these return semantic tokens: ```swift Text("Hello") .foregroundColor(.palette.accent) // resolves to palette's accent color - .background(.palette.containerBodyBackground) + .background(.palette.surfaceBackground) ``` Available semantic tokens include: @@ -112,10 +112,12 @@ Available semantic tokens include: |-------|-------------| | `.palette.foreground` | Primary text | | `.palette.foregroundSecondary` | Secondary / dimmed text | +| `.palette.foregroundTertiary` | Disabled / muted text | | `.palette.accent` | Highlighted elements, titles | | `.palette.border` | Container borders | -| `.palette.containerBodyBackground` | Container body / content area background | -| `.palette.containerCapBackground` | Container header / footer background | +| `.palette.surfaceBackground` | Container body / content area (block appearance) | +| `.palette.surfaceHeaderBackground` | Container header / footer (block appearance) | +| `.palette.elevatedBackground` | Buttons, interactive surfaces (block appearance) | | `.palette.success` / `.warning` / `.error` / `.info` | Status indicators | ### In renderToBuffer (with RenderContext) diff --git a/Sources/TUIkit/TUIkit.docc/Articles/PaletteReference.md b/Sources/TUIkit/TUIkit.docc/Articles/PaletteReference.md index b65d7a8..b9d7405 100644 --- a/Sources/TUIkit/TUIkit.docc/Articles/PaletteReference.md +++ b/Sources/TUIkit/TUIkit.docc/Articles/PaletteReference.md @@ -4,14 +4,14 @@ A visual reference for all built-in color palettes with their exact color values ## Overview -TUIkit ships with **7 palettes** — 5 handcrafted phosphor themes, 1 classic ncurses theme, and 1 violet theme generated from a single base hue. Each palette defines semantic color tokens that the framework resolves at render time. +TUIkit ships with **6 palettes** — 5 handcrafted phosphor themes and 1 violet theme generated from a single base hue. Each palette defines semantic color tokens that the framework resolves at render time. Users access palette colors via `Color.palette.*`: ```swift Text("Hello") .foregroundColor(.palette.accent) - .background(.palette.containerBodyBackground) + .background(.palette.surfaceBackground) ``` Cycle through palettes at runtime by pressing `t` (default binding), or set a specific palette programmatically: @@ -20,19 +20,39 @@ Cycle through palettes at runtime by pressing `t` (default binding), or set a sp environment.paletteManager.setCurrent(AmberPalette()) ``` -## Color Token Categories +## Protocol Hierarchy -Every palette must define colors for these 6 categories: +TUIkit uses a two-level palette protocol: + +- **``Palette``** — 13 essential color tokens (8 required, 5 with defaults) +- **``BlockPalette``** — Extends `Palette` with 3 surface backgrounds for block-style appearances + +``` +Palette (13 properties) +├── Required: background, foreground, accent, border, +│ success, warning, error, info +├── Defaults: statusBarBackground, appHeaderBackground, overlayBackground, +│ foregroundSecondary, foregroundTertiary +└── BlockPalette: Palette (+3 computed properties) + ├── surfaceBackground (background.lighter(by: 0.08)) + ├── surfaceHeaderBackground (background.lighter(by: 0.05)) + └── elevatedBackground (surfaceHeaderBackground.lighter(by: 0.05)) +``` + +All 6 built-in palettes conform to ``BlockPalette``. Custom palettes can conform to just ``Palette`` if they don't need block-style surfaces. + +## Color Token Categories | Category | Tokens | Purpose | |----------|--------|---------| -| **Background** | `background`, `containerBodyBackground`, `containerCapBackground`, `buttonBackground`, `statusBarBackground`, `appHeaderBackground`, `overlayBackground` | App background, container body/cap, buttons, status bar, overlays | +| **Background** | `background`, `statusBarBackground`, `appHeaderBackground`, `overlayBackground` | App background, status bar, overlays | +| **Block Surfaces** | `surfaceBackground`, `surfaceHeaderBackground`, `elevatedBackground` | Container body, headers, buttons (block appearance only) | | **Foreground** | `foreground`, `foregroundSecondary`, `foregroundTertiary` | Primary, secondary, and tertiary text | -| **Accent** | `accent`, `accentSecondary` | Interactive elements, highlights | +| **Accent** | `accent` | Interactive elements, highlights | | **Semantic** | `success`, `warning`, `error`, `info` | Status indicators | | **UI Elements** | `border` | Borders | -Only 10 tokens are required — the remaining 8 have sensible defaults derived from the required ones. See for details on creating custom palettes. +Only 8 tokens are required — the remaining have sensible defaults. See for details on creating custom palettes. ## Green (Default) @@ -45,13 +65,10 @@ Inspired by P1 phosphor CRT monitors (IBM 5151, Apple II). This is the default p | Token | Hex | RGB | Description | |-------|-----|-----|-------------| | `background` | `#060A07` | (6, 10, 7) | Near-black with green tint | -| `containerBodyBackground` | `#0E271C` | (14, 39, 28) | Container content area | -| `containerCapBackground` | `#0A1B13` | (10, 27, 19) | Container header/footer | | `foreground` | `#33FF33` | (51, 255, 51) | Classic phosphor green | | `foregroundSecondary` | `#27C227` | (39, 194, 39) | Dimmer green | | `foregroundTertiary` | `#1F8F1F` | (31, 143, 31) | Subtle green | | `accent` | `#66FF66` | (102, 255, 102) | Bright green highlight | -| `accentSecondary` | `#00CC00` | (0, 204, 0) | Medium green | ### Semantic Colors @@ -68,7 +85,14 @@ Inspired by P1 phosphor CRT monitors (IBM 5151, Apple II). This is the default p |-------|-----|-----| | `border` | `#2D5A2D` | (45, 90, 45) | | `statusBarBackground` | `#0F2215` | (15, 34, 21) | -| `buttonBackground` | `#145523` | (20, 85, 35) | + +### Block Surfaces (computed from background) + +| Token | Description | +|-------|-------------| +| `surfaceBackground` | Container body area | +| `surfaceHeaderBackground` | Container header/footer | +| `elevatedBackground` | Buttons, interactive surfaces | ## Amber @@ -81,13 +105,10 @@ Inspired by P3 phosphor CRT monitors (IBM 3278, Wyse 50). Warm amber tones remin | Token | Hex | RGB | Description | |-------|-----|-----|-------------| | `background` | `#0A0706` | (10, 7, 6) | Near-black with warm tint | -| `containerBodyBackground` | `#251710` | (37, 23, 16) | Container content area | -| `containerCapBackground` | `#1E110E` | (30, 17, 14) | Container header/footer | | `foreground` | `#FFAA00` | (255, 170, 0) | Classic amber phosphor | | `foregroundSecondary` | `#CC8800` | (204, 136, 0) | Dimmer amber | | `foregroundTertiary` | `#8F6600` | (143, 102, 0) | Subtle amber | | `accent` | `#FFCC33` | (255, 204, 51) | Bright amber highlight | -| `accentSecondary` | `#CC9900` | (204, 153, 0) | Medium amber | ### Semantic Colors @@ -104,7 +125,6 @@ Inspired by P3 phosphor CRT monitors (IBM 3278, Wyse 50). Warm amber tones remin |-------|-----|-----| | `border` | `#5A4A2D` | (90, 74, 45) | | `statusBarBackground` | `#191613` | (25, 22, 19) | -| `buttonBackground` | `#3A2A1D` | (58, 42, 29) | ## White @@ -117,13 +137,10 @@ Inspired by P4 phosphor CRT monitors (DEC VT100, VT220). Clean monochrome with c | Token | Hex | RGB | Description | |-------|-----|-----|-------------| | `background` | `#06070A` | (6, 7, 10) | Near-black with blue tint | -| `containerBodyBackground` | `#111A2A` | (17, 26, 42) | Container content area | -| `containerCapBackground` | `#0D131D` | (13, 19, 29) | Container header/footer | | `foreground` | `#E8E8E8` | (232, 232, 232) | Off-white text | | `foregroundSecondary` | `#B0B0B0` | (176, 176, 176) | Light gray | | `foregroundTertiary` | `#787878` | (120, 120, 120) | Medium gray | | `accent` | `#FFFFFF` | (255, 255, 255) | Pure white highlight | -| `accentSecondary` | `#C0C0C0` | (192, 192, 192) | Silver | ### Semantic Colors @@ -140,7 +157,6 @@ Inspired by P4 phosphor CRT monitors (DEC VT100, VT220). Clean monochrome with c |-------|-----|-----| | `border` | `#484848` | (72, 72, 72) | | `statusBarBackground` | `#131619` | (19, 22, 25) | -| `buttonBackground` | `#1D2535` | (29, 37, 53) | ## Red @@ -153,13 +169,10 @@ Inspired by military and night-vision-friendly displays. Preserves scotopic (nig | Token | Hex | RGB | Description | |-------|-----|-----|-------------| | `background` | `#0A0606` | (10, 6, 6) | Near-black with red tint | -| `containerBodyBackground` | `#281112` | (40, 17, 18) | Container content area | -| `containerCapBackground` | `#1E0F10` | (30, 15, 16) | Container header/footer | | `foreground` | `#FF4444` | (255, 68, 68) | Bright red text | | `foregroundSecondary` | `#CC3333` | (204, 51, 51) | Dimmer red | | `foregroundTertiary` | `#8F2222` | (143, 34, 34) | Subtle red | | `accent` | `#FF6666` | (255, 102, 102) | Light red highlight | -| `accentSecondary` | `#CC4444` | (204, 68, 68) | Medium red | ### Semantic Colors @@ -176,45 +189,6 @@ Inspired by military and night-vision-friendly displays. Preserves scotopic (nig |-------|-----|-----| | `border` | `#5A2D2D` | (90, 45, 45) | | `statusBarBackground` | `#191313` | (25, 19, 19) | -| `buttonBackground` | `#3A1F22` | (58, 31, 34) | - -## ncurses - -Classic ncurses terminal colors (htop, Midnight Commander, vim). The only palette using standard ANSI colors instead of RGB — maximum compatibility with 16-color terminals. - -**Palette type:** ``NCursesPalette`` · **ID:** `"ncurses"` - -### Core Colors - -| Token | ANSI Color | Code | Description | -|-------|-----------|------|-------------| -| `background` | Black | 30/40 | Terminal default black | -| `containerBodyBackground` | Blue | 34/44 | Classic ncurses panel blue | -| `containerCapBackground` | Bright Black | 90/100 | Dark gray | -| `foreground` | White | 37/47 | Standard white | -| `foregroundSecondary` | Bright White | 97/107 | Brighter white | -| `foregroundTertiary` | Bright Black | 90/100 | Gray (dimmed) | -| `accent` | Cyan | 36/46 | Primary interactive color | -| `accentSecondary` | Bright Cyan | 96/106 | Highlighted interactive | - -### Semantic Colors - -| Token | ANSI Color | Code | Description | -|-------|-----------|------|-------------| -| `success` | Green | 32/42 | Standard green | -| `warning` | Yellow | 33/43 | Standard yellow | -| `error` | Red | 31/41 | Standard red | -| `info` | Cyan | 36/46 | Same as accent | - -### UI Elements - -| Token | ANSI Color | Code | -|-------|-----------|------| -| `border` | White | 37/47 | -| `statusBarBackground` | Blue | 34/44 | -| `buttonBackground` | Bright Blue | 94/104 | - -> Tip: Use the ncurses palette when targeting terminals with limited color support (SSH sessions, older terminal emulators, or 16-color environments). ## Violet @@ -226,9 +200,9 @@ An algorithmically generated palette based on HSL color theory with a base hue o ``VioletPalette`` takes a base hue (270°) and derives all color tokens using HSL relationships: -- **Backgrounds** — Base hue at very low lightness (3–10%) with reduced saturation +- **Background** — Base hue at very low lightness (3%) with reduced saturation - **Foregrounds** — Base hue at medium-high lightness (40–70%) -- **Accents** — Base hue at high lightness (78%) with high saturation +- **Accent** — Base hue at high lightness (78%) with high saturation - **Semantic colors** — Derived from color theory offsets: - `success` = base + 120° (triadic) - `warning` = base + 60° (analogous warm) @@ -240,20 +214,48 @@ An algorithmically generated palette based on HSL color theory with a base hue o | Token | HSL | Description | |-------|-----|-------------| | `background` | hsl(270, 30%, 3%) | Near-black with violet tint | -| `containerBodyBackground` | hsl(270, 40%, 10%) | Container content area | -| `containerCapBackground` | hsl(270, 35%, 7%) | Container header/footer | | `foreground` | hsl(270, 80%, 70%) | Light violet text | | `foregroundSecondary` | hsl(270, 70%, 55%) | Medium violet | | `foregroundTertiary` | hsl(270, 60%, 40%) | Dim violet | | `accent` | hsl(270, 85%, 78%) | Bright lavender | -| `accentSecondary` | hsl(270, 75%, 50%) | Medium purple | | `success` | hsl(30, 70%, 65%) | Warm orange (270+120=30°) | | `warning` | hsl(330, 80%, 70%) | Pink (270+60=330°) | | `error` | hsl(90, 85%, 65%) | Lime green (270+180=90°) | | `info` | hsl(210, 70%, 70%) | Sky blue (270−60=210°) | | `border` | hsl(270, 40%, 25%) | Dark purple border | | `statusBarBackground` | hsl(270, 35%, 8%) | Very dark violet | -| `buttonBackground` | hsl(270, 45%, 15%) | Dark purple | + +## Blue + +Inspired by vintage vacuum fluorescent displays (VFDs). The characteristic bright cyan-blue glow. + +**Palette type:** ``BluePalette`` · **ID:** `"blue"` + +### Core Colors + +| Token | Hex | RGB | Description | +|-------|-----|-----|-------------| +| `background` | `#060708` | (6, 7, 8) | Near-black with blue tint | +| `foreground` | `#00AAFF` | (0, 170, 255) | Bright VFD blue | +| `foregroundSecondary` | `#0088CC` | (0, 136, 204) | Medium blue | +| `foregroundTertiary` | `#006699` | (0, 102, 153) | Dim blue | +| `accent` | `#33BBFF` | (51, 187, 255) | Lighter blue highlight | + +### Semantic Colors + +| Token | Hex | RGB | Description | +|-------|-----|-----|-------------| +| `success` | `#33CCFF` | (51, 204, 255) | Cyan-blue | +| `warning` | `#66CCFF` | (102, 204, 255) | Light cyan | +| `error` | `#FF6633` | (255, 102, 51) | Orange-red contrast | +| `info` | `#99DDFF` | (153, 221, 255) | Pale blue | + +### UI Elements + +| Token | Hex | RGB | +|-------|-----|-----| +| `border` | `#2D4A5A` | (45, 74, 90) | +| `statusBarBackground` | `#0F1822` | (15, 24, 34) | ## Palette Cycling Order @@ -267,7 +269,6 @@ When pressing `t` to cycle themes, palettes rotate in this order: | 4 | Violet | HSL-generated (hue 270°) | | 5 | Blue | Handcrafted (VFD) | | 6 | White | Handcrafted | -| 7 | ncurses | Handcrafted (ANSI) | ## Color Resolution Flow @@ -276,22 +277,26 @@ When you write `.foregroundColor(.palette.accent)`, TUIkit resolves the actual c 1. **Declaration** — `Color.palette.accent` creates a `Color` with a semantic token (`.accent`) 2. **Render pass** — The current palette is read from `context.environment.palette` 3. **Resolution** — The semantic token maps to the palette's `accent` property -4. **ANSI output** — The resolved RGB/ANSI color is converted to terminal escape codes +4. **BlockPalette tokens** — For `surfaceBackground` etc., the palette is cast to ``BlockPalette``; if the cast fails, `background` is used as fallback +5. **ANSI output** — The resolved RGB/ANSI color is converted to terminal escape codes This means the same view code produces different colors depending on the active palette — no code changes needed when switching themes. ## Topics -### Palettes +### Protocols - ``Palette`` +- ``BlockPalette`` + +### Palettes + - ``GreenPalette`` - ``AmberPalette`` - ``RedPalette`` - ``VioletPalette`` - ``BluePalette`` - ``WhitePalette`` -- ``NCursesPalette`` ### Color System diff --git a/Sources/TUIkit/TUIkit.docc/Articles/ThemingGuide.md b/Sources/TUIkit/TUIkit.docc/Articles/ThemingGuide.md index 5b192be..d1c8851 100644 --- a/Sources/TUIkit/TUIkit.docc/Articles/ThemingGuide.md +++ b/Sources/TUIkit/TUIkit.docc/Articles/ThemingGuide.md @@ -4,7 +4,7 @@ Customize the visual appearance of your TUIkit application with palettes. ## Overview -TUIkit includes a full theming system with seven built-in palettes inspired by classic CRT terminals. Palettes define semantic colors for backgrounds, foregrounds, accents, and UI elements. +TUIkit includes a full theming system with six built-in palettes inspired by classic CRT terminals. Palettes define semantic colors for backgrounds, foregrounds, accents, and UI elements. ## Built-in Palettes @@ -16,7 +16,6 @@ TUIkit includes a full theming system with seven built-in palettes inspired by c | Violet | ``VioletPalette`` | Retro sci-fi displays | | Blue | ``BluePalette`` | VFD displays | | White | ``WhitePalette`` | DEC VT100, VT220 | -| ncurses | ``NCursesPalette`` | Classic ncurses apps | ## Using Palettes @@ -55,7 +54,7 @@ Use ``Color/palette`` to access the current palette's colors: ```swift Text("Styled text") .foregroundColor(.palette.foreground) - .backgroundColor(.palette.containerBodyBackground) + .backgroundColor(.palette.surfaceBackground) ``` Or read the palette directly from the environment: @@ -68,7 +67,9 @@ Text("Hello").foregroundColor(palette.accent) ## Creating Custom Palettes -Implement the ``Palette`` protocol: +### Minimal Palette (Palette protocol) + +Implement the ``Palette`` protocol for a palette with just the essential colors: ```swift struct MyCustomPalette: Palette { @@ -78,20 +79,53 @@ struct MyCustomPalette: Palette { let background = Color.hex(0x1A1A2E) let foreground = Color.hex(0xE0E0E0) let accent = Color.hex(0x00D4FF) + let success = Color.hex(0x00FF88) + let warning = Color.hex(0xFFCC00) + let error = Color.hex(0xFF4444) + let info = Color.hex(0x44AAFF) + let border = Color.hex(0x333355) - // ... implement remaining required properties - // Many have default implementations via Palette extension + // Optional: override defaults for statusBarBackground, + // appHeaderBackground, overlayBackground, + // foregroundSecondary, foregroundTertiary } ``` +### Block-Aware Palette (BlockPalette protocol) + +For palettes that provide block-appearance surface colors, conform to ``BlockPalette``: + +```swift +struct MyBlockPalette: BlockPalette { + let id = "custom-block" + let name = "Custom Block" + + // ... same required properties as Palette ... + + // Optional: override computed defaults + // var surfaceBackground: Color { ... } + // var surfaceHeaderBackground: Color { ... } + // var elevatedBackground: Color { ... } +} +``` + +The ``BlockPalette`` defaults compute surface colors from `background` using `lighter(by:)`: +- `surfaceBackground` = `background.lighter(by: 0.08)` +- `surfaceHeaderBackground` = `background.lighter(by: 0.05)` +- `elevatedBackground` = `surfaceHeaderBackground.lighter(by: 0.05)` + ## Palette Color Properties -The ``Palette`` protocol defines these semantic color categories: +### Palette (Base Protocol) -- **Backgrounds**: `background`, `containerBodyBackground`, `containerCapBackground`, `buttonBackground`, `statusBarBackground`, `appHeaderBackground`, `overlayBackground` +- **Backgrounds**: `background`, `statusBarBackground`, `appHeaderBackground`, `overlayBackground` - **Foregrounds**: `foreground`, `foregroundSecondary`, `foregroundTertiary` -- **Accents**: `accent`, `accentSecondary` +- **Accent**: `accent` - **Semantic**: `success`, `warning`, `error`, `info` - **UI Elements**: `border` -Many of these have default implementations that derive from the primary colors, so a minimal palette only needs to define a handful of values. +### BlockPalette (extends Palette) + +- **Surfaces**: `surfaceBackground`, `surfaceHeaderBackground`, `elevatedBackground` + +Only 8 properties are required (`background`, `foreground`, `accent`, `border`, `success`, `warning`, `error`, `info`). All others have default implementations that derive from these. diff --git a/Sources/TUIkit/TUIkit.docc/TUIkit.md b/Sources/TUIkit/TUIkit.docc/TUIkit.md index 1b96455..92bc444 100644 --- a/Sources/TUIkit/TUIkit.docc/TUIkit.md +++ b/Sources/TUIkit/TUIkit.docc/TUIkit.md @@ -114,12 +114,14 @@ struct MyApp: App { ### Theming - ``Palette`` +- ``BlockPalette`` - ``ThemeManager`` - ``GreenPalette`` - ``AmberPalette`` - ``WhitePalette`` - ``RedPalette`` -- ``NCursesPalette`` +- ``BluePalette`` +- ``VioletPalette`` - ``GeneratedPalette`` ### Colors diff --git a/Sources/TUIkit/Views/Button.swift b/Sources/TUIkit/Views/Button.swift index 2d7e55c..ef32146 100644 --- a/Sources/TUIkit/Views/Button.swift +++ b/Sources/TUIkit/Views/Button.swift @@ -240,10 +240,10 @@ extension Button: Renderable { textStyle.backgroundColor = currentStyle.backgroundColor?.resolve(with: palette) textStyle.isBold = currentStyle.isBold && !isDisabled - // In block appearance, add buttonBackground to button + // In block appearance, add elevated background to button let isBlockAppearance = context.environment.appearance.rawId == .block if isBlockAppearance && textStyle.backgroundColor == nil { - textStyle.backgroundColor = context.environment.palette.buttonBackground + textStyle.backgroundColor = context.environment.palette.blockElevatedBackground } let styledLabel = ANSIRenderer.render(paddedLabel, with: textStyle) diff --git a/Sources/TUIkit/Views/ContainerView.swift b/Sources/TUIkit/Views/ContainerView.swift index 4403b88..7ab8fc3 100644 --- a/Sources/TUIkit/Views/ContainerView.swift +++ b/Sources/TUIkit/Views/ContainerView.swift @@ -358,7 +358,7 @@ extension ContainerView: Renderable { } // Body lines with theme background - let bodyBg = context.environment.palette.containerBodyBackground + let bodyBg = context.environment.palette.blockSurfaceBackground for line in bodyBuffer.lines { lines.append( BorderRenderer.standardContentLine( @@ -435,10 +435,10 @@ extension ContainerView: Renderable { var lines: [String] = [] // Get palette colors for block appearance - // Header/Footer = darker background - // Body = lighter background (containerBodyBackground) - let headerFooterBg = palette.containerCapBackground - let bodyBg = palette.containerBodyBackground + // Header/Footer = darker background (surfaceHeaderBackground) + // Body = lighter background (surfaceBackground) + let headerFooterBg = palette.blockSurfaceHeaderBackground + let bodyBg = palette.blockSurfaceBackground let hasHeader = title != nil let hasFooter = footerBuffer != nil && !(footerBuffer?.isEmpty ?? true) diff --git a/Sources/TUIkit/Views/Menu.swift b/Sources/TUIkit/Views/Menu.swift index 9a74e46..3a192bf 100644 --- a/Sources/TUIkit/Views/Menu.swift +++ b/Sources/TUIkit/Views/Menu.swift @@ -236,7 +236,7 @@ extension Menu: Renderable { style.isBold = true style.foregroundColor = selectedColor?.resolve(with: palette) ?? palette.accent // Use a dimmed version of the accent color for background - style.backgroundColor = palette.containerBodyBackground + style.backgroundColor = palette.blockSurfaceBackground } else { // Use palette foreground color if no custom itemColor is set style.foregroundColor = itemColor?.resolve(with: palette) ?? palette.foreground @@ -348,8 +348,8 @@ extension Menu: Renderable { var result: [String] = [] if isBlockStyle { - let headerFooterBg = palette.containerCapBackground - let bodyBg = palette.containerBodyBackground + let headerFooterBg = palette.blockSurfaceHeaderBackground + let bodyBg = palette.blockSurfaceBackground let hasHeader = dividerLineIndex != nil // Top border diff --git a/Tests/TUIkitTests/PaletteDefaultTests.swift b/Tests/TUIkitTests/PaletteDefaultTests.swift index a3e9da5..c2f61ae 100644 --- a/Tests/TUIkitTests/PaletteDefaultTests.swift +++ b/Tests/TUIkitTests/PaletteDefaultTests.swift @@ -2,7 +2,7 @@ // PaletteDefaultTests.swift // TUIkit // -// Tests for Palette protocol default implementations. +// Tests for Palette and BlockPalette protocol default implementations. // import Testing @@ -24,37 +24,34 @@ private struct MinimalPalette: Palette { let border = Color.brightBlack } +/// Minimal block palette that inherits Palette requirements +/// and uses computed BlockPalette defaults. +private struct MinimalBlockPalette: BlockPalette { + let id = "minimal-block" + let name = "Minimal Block" + let background = Color.hex(0x0A0A0A) + let foreground = Color.white + let accent = Color.cyan + let success = Color.green + let warning = Color.yellow + let error = Color.red + let info = Color.blue + let border = Color.brightBlack +} + @Suite("Palette Default Implementation Tests") struct PaletteDefaultTests { - @Test("Defaults derive containerBodyBackground from background") - func defaultContainerBodyBackground() { - let palette = MinimalPalette() - #expect(palette.containerBodyBackground == palette.background) - } - - @Test("Defaults derive containerCapBackground from background") - func defaultContainerCapBackground() { - let palette = MinimalPalette() - #expect(palette.containerCapBackground == palette.background) - } - @Test("Defaults derive foregroundSecondary from foreground") func defaultForegroundSecondary() { let palette = MinimalPalette() #expect(palette.foregroundSecondary == palette.foreground) } - @Test("Defaults derive foregroundPlaceholder from foregroundTertiary") - func defaultForegroundPlaceholder() { + @Test("Defaults derive foregroundTertiary from foreground") + func defaultForegroundTertiary() { let palette = MinimalPalette() - #expect(palette.foregroundPlaceholder == palette.foregroundTertiary) - } - - @Test("Defaults derive accentSecondary from accent") - func defaultAccentSecondary() { - let palette = MinimalPalette() - #expect(palette.accentSecondary == palette.accent) + #expect(palette.foregroundTertiary == palette.foreground) } @Test("Defaults derive statusBarBackground from background") @@ -63,11 +60,57 @@ struct PaletteDefaultTests { #expect(palette.statusBarBackground == palette.background) } - @Test("Defaults derive container and button backgrounds") - func defaultContainerColors() { + @Test("Defaults derive appHeaderBackground from background") + func defaultAppHeaderBackground() { + let palette = MinimalPalette() + #expect(palette.appHeaderBackground == palette.background) + } + + @Test("Defaults derive overlayBackground from background") + func defaultOverlayBackground() { let palette = MinimalPalette() - #expect(palette.buttonBackground == palette.containerCapBackground) - #expect(palette.appHeaderBackground == palette.containerCapBackground) #expect(palette.overlayBackground == palette.background) } } + +@Suite("BlockPalette Default Implementation Tests") +struct BlockPaletteDefaultTests { + + @Test("BlockPalette surfaceBackground is lighter than background") + func surfaceBackgroundIsLighter() { + let palette = MinimalBlockPalette() + let bgComponents = palette.background.rgbComponents! + let surfaceComponents = palette.surfaceBackground.rgbComponents! + let bgBrightness = Int(bgComponents.red) + Int(bgComponents.green) + Int(bgComponents.blue) + let surfaceBrightness = Int(surfaceComponents.red) + Int(surfaceComponents.green) + Int(surfaceComponents.blue) + #expect(surfaceBrightness > bgBrightness, "surfaceBackground should be brighter than background") + } + + @Test("BlockPalette surfaceHeaderBackground is lighter than background") + func surfaceHeaderBackgroundIsLighter() { + let palette = MinimalBlockPalette() + let bgComponents = palette.background.rgbComponents! + let headerComponents = palette.surfaceHeaderBackground.rgbComponents! + let bgBrightness = Int(bgComponents.red) + Int(bgComponents.green) + Int(bgComponents.blue) + let headerBrightness = Int(headerComponents.red) + Int(headerComponents.green) + Int(headerComponents.blue) + #expect(headerBrightness > bgBrightness, "surfaceHeaderBackground should be brighter than background") + } + + @Test("BlockPalette elevatedBackground is lighter than surfaceHeaderBackground") + func elevatedBackgroundIsLighter() { + let palette = MinimalBlockPalette() + let headerComponents = palette.surfaceHeaderBackground.rgbComponents! + let elevatedComponents = palette.elevatedBackground.rgbComponents! + let headerBrightness = Int(headerComponents.red) + Int(headerComponents.green) + Int(headerComponents.blue) + let elevatedBrightness = Int(elevatedComponents.red) + Int(elevatedComponents.green) + Int(elevatedComponents.blue) + #expect(elevatedBrightness > headerBrightness, "elevatedBackground should be brighter than surfaceHeaderBackground") + } + + @Test("Non-BlockPalette falls back to background via convenience accessors") + func nonBlockPaletteFallback() { + let palette = MinimalPalette() + #expect(palette.blockSurfaceBackground == palette.background) + #expect(palette.blockSurfaceHeaderBackground == palette.background) + #expect(palette.blockElevatedBackground == palette.background) + } +} diff --git a/Tests/TUIkitTests/PaletteRegistryTests.swift b/Tests/TUIkitTests/PaletteRegistryTests.swift index a7510ca..2050af9 100644 --- a/Tests/TUIkitTests/PaletteRegistryTests.swift +++ b/Tests/TUIkitTests/PaletteRegistryTests.swift @@ -14,8 +14,8 @@ struct PaletteRegistryTests { @Test("Registry contains all predefined palettes") func registryCount() { - // Green, Amber, Red, Violet, Blue, White, NCurses = 7 - #expect(PaletteRegistry.all.count == 7) + // Green, Amber, Red, Violet, Blue, White = 6 + #expect(PaletteRegistry.all.count == 6) } @Test("Registry cycling order follows color spectrum") @@ -26,7 +26,6 @@ struct PaletteRegistryTests { #expect(PaletteRegistry.all[3].id == "violet") #expect(PaletteRegistry.all[4].id == "blue") #expect(PaletteRegistry.all[5].id == "white") - #expect(PaletteRegistry.all[6].id == "ncurses") } @Test("Registry finds palette by ID") diff --git a/Tests/TUIkitTests/PredefinedPaletteTests.swift b/Tests/TUIkitTests/PredefinedPaletteTests.swift index e39e836..f5bb3da 100644 --- a/Tests/TUIkitTests/PredefinedPaletteTests.swift +++ b/Tests/TUIkitTests/PredefinedPaletteTests.swift @@ -2,7 +2,7 @@ // PredefinedPaletteTests.swift // TUIkit // -// Tests for all predefined palette structs: Green, Amber, Red, Violet, Blue, White, NCurses. +// Tests for all predefined palette structs: Green, Amber, Red, Violet, Blue, White. // import Testing @@ -37,36 +37,28 @@ struct GreenPaletteTests { #expect(palette.name == "Green") } - @Test("Green palette backgrounds get progressively brighter from app to container to buttons") - func greenBackgroundLuminanceOrder() throws { + @Test("Green palette block surfaces get progressively brighter") + func greenBlockSurfaceLuminanceOrder() throws { let palette = GreenPalette() let bgLum = try #require(relativeLuminance(of: palette.background)) - let bodyLum = try #require(relativeLuminance(of: palette.containerBodyBackground)) - let capLum = try #require(relativeLuminance(of: palette.containerCapBackground)) - let buttonLum = try #require(relativeLuminance(of: palette.buttonBackground)) + let headerLum = try #require(relativeLuminance(of: palette.surfaceHeaderBackground)) + let surfaceLum = try #require(relativeLuminance(of: palette.surfaceBackground)) + let elevatedLum = try #require(relativeLuminance(of: palette.elevatedBackground)) - #expect(bgLum < capLum, "background should be darker than containerCapBackground") - #expect(bgLum < bodyLum, "background should be darker than containerBodyBackground") - #expect(buttonLum > capLum, "buttonBackground should be brighter than containerCapBackground") + #expect(bgLum < headerLum, "background should be darker than surfaceHeaderBackground") + #expect(bgLum < surfaceLum, "background should be darker than surfaceBackground") + #expect(elevatedLum > headerLum, "elevatedBackground should be brighter than surfaceHeaderBackground") } - @Test("Green palette foregrounds get progressively dimmer including placeholder") + @Test("Green palette foregrounds get progressively dimmer") func greenForegroundLuminanceOrder() throws { let palette = GreenPalette() let fgLum = try #require(relativeLuminance(of: palette.foreground)) let fgSecLum = try #require(relativeLuminance(of: palette.foregroundSecondary)) let fgTerLum = try #require(relativeLuminance(of: palette.foregroundTertiary)) - let fgPlaceLum = try #require(relativeLuminance(of: palette.foregroundPlaceholder)) #expect(fgLum > fgSecLum, "foreground should be brighter than foregroundSecondary") #expect(fgSecLum > fgTerLum, "foregroundSecondary should be brighter than foregroundTertiary") - #expect(fgTerLum > fgPlaceLum, "foregroundTertiary should be brighter than foregroundPlaceholder") - } - - @Test("Green palette has distinct accent colors") - func greenAccents() { - let palette = GreenPalette() - #expect(palette.accent != palette.accentSecondary) } @Test("Green palette has all semantic colors") @@ -136,17 +128,17 @@ struct VioletPaletteTests { #expect(palette.name == "Violet") } - @Test("Violet palette backgrounds get progressively brighter from app to container to buttons") - func violetBackgroundLuminanceOrder() throws { + @Test("Violet palette block surfaces get progressively brighter") + func violetBlockSurfaceLuminanceOrder() throws { let palette = VioletPalette() let bgLum = try #require(relativeLuminance(of: palette.background)) - let bodyLum = try #require(relativeLuminance(of: palette.containerBodyBackground)) - let capLum = try #require(relativeLuminance(of: palette.containerCapBackground)) - let buttonLum = try #require(relativeLuminance(of: palette.buttonBackground)) + let headerLum = try #require(relativeLuminance(of: palette.surfaceHeaderBackground)) + let surfaceLum = try #require(relativeLuminance(of: palette.surfaceBackground)) + let elevatedLum = try #require(relativeLuminance(of: palette.elevatedBackground)) - #expect(bgLum < capLum, "background should be darker than containerCapBackground") - #expect(bgLum < bodyLum, "background should be darker than containerBodyBackground") - #expect(buttonLum > capLum, "buttonBackground should be brighter than containerCapBackground") + #expect(bgLum < headerLum, "background should be darker than surfaceHeaderBackground") + #expect(bgLum < surfaceLum, "background should be darker than surfaceBackground") + #expect(elevatedLum > headerLum, "elevatedBackground should be brighter than surfaceHeaderBackground") } @Test("Violet palette colors differ from green palette") @@ -170,17 +162,17 @@ struct BluePaletteTests { #expect(palette.name == "Blue") } - @Test("Blue palette backgrounds get progressively brighter from app to container to buttons") - func blueBackgroundLuminanceOrder() throws { + @Test("Blue palette block surfaces get progressively brighter") + func blueBlockSurfaceLuminanceOrder() throws { let palette = BluePalette() let bgLum = try #require(relativeLuminance(of: palette.background)) - let bodyLum = try #require(relativeLuminance(of: palette.containerBodyBackground)) - let capLum = try #require(relativeLuminance(of: palette.containerCapBackground)) - let buttonLum = try #require(relativeLuminance(of: palette.buttonBackground)) + let headerLum = try #require(relativeLuminance(of: palette.surfaceHeaderBackground)) + let surfaceLum = try #require(relativeLuminance(of: palette.surfaceBackground)) + let elevatedLum = try #require(relativeLuminance(of: palette.elevatedBackground)) - #expect(bgLum < capLum, "background should be darker than containerCapBackground") - #expect(bgLum < bodyLum, "background should be darker than containerBodyBackground") - #expect(buttonLum > capLum, "buttonBackground should be brighter than containerCapBackground") + #expect(bgLum < headerLum, "background should be darker than surfaceHeaderBackground") + #expect(bgLum < surfaceLum, "background should be darker than surfaceBackground") + #expect(elevatedLum > headerLum, "elevatedBackground should be brighter than surfaceHeaderBackground") } @Test("Blue palette colors differ from violet palette") @@ -191,28 +183,3 @@ struct BluePaletteTests { #expect(blue.accent != violet.accent) } } - -// MARK: - NCurses Palette Tests - -@Suite("NCurses Palette Tests") -struct NCursesPaletteTests { - - @Test("NCurses palette has correct identity") - func ncursesIdentity() { - let palette = NCursesPalette() - #expect(palette.id == "ncurses") - #expect(palette.name == "ncurses") - } - - @Test("NCurses palette uses standard terminal colors") - func ncursesUsesStandardColors() { - let palette = NCursesPalette() - #expect(palette.background == .black) - #expect(palette.foreground == .white) - #expect(palette.accent == .cyan) - #expect(palette.success == .green) - #expect(palette.warning == .yellow) - #expect(palette.error == .red) - } - -} diff --git a/palette-preview.html b/palette-preview.html index 6d2b378..0710df5 100644 --- a/palette-preview.html +++ b/palette-preview.html @@ -272,7 +272,7 @@

TUIKit Color Palettes

-

5 new palettes designed for terminal UI aesthetics — plus existing palettes for reference

+

5 proposed palettes with Palette (13) + BlockPalette (3) properties — plus existing palettes for reference

@@ -313,23 +313,21 @@ const newPalettes = [ desc: "Inspired by LCARS (Star Trek) terminal displays, teal control panels, and bridge consoles.", colors: { background: "#060A0A", - containerBodyBackground: "#0E2526", - containerCapBackground: "#0A1B1C", foreground: "#00DDC0", foregroundSecondary: "#00AA94", foregroundTertiary: "#007A6B", - foregroundPlaceholder: "#00574C", accent: "#33FFE0", - accentSecondary: "#00BB9E", success: "#33FFAA", warning: "#CCFF66", error: "#FF6633", info: "#66FFEE", border: "#2D5A55", statusBarBackground: "#0D1E1D", - buttonBackground: "#144542", appHeaderBackground: "#0A1B1C", overlayBackground: "#060A0A", + surfaceBackground: "#0E2526", + surfaceHeaderBackground: "#0A1B1C", + elevatedBackground: "#144542", } }, { @@ -337,23 +335,21 @@ const newPalettes = [ desc: "Synthwave neon glow \u2014 inspired by 80s arcade cabinets, retrowave, and cyberpunk HUDs.", colors: { background: "#0A060A", - containerBodyBackground: "#251428", - containerCapBackground: "#1C0E1E", foreground: "#FF44CC", foregroundSecondary: "#CC33A3", foregroundTertiary: "#8F2472", - foregroundPlaceholder: "#661952", accent: "#FF77DD", - accentSecondary: "#DD33AA", success: "#FF88DD", warning: "#FFAA77", error: "#FF6633", info: "#FF99EE", border: "#5A2D50", statusBarBackground: "#180D19", - buttonBackground: "#3A1D3A", appHeaderBackground: "#1C0E1E", overlayBackground: "#0A060A", + surfaceBackground: "#251428", + surfaceHeaderBackground: "#1C0E1E", + elevatedBackground: "#3A1D3A", } }, { @@ -361,23 +357,21 @@ const newPalettes = [ desc: "Warm brass instrument panels, steampunk gauges, and luxury cockpit displays.", colors: { background: "#0A0906", - containerBodyBackground: "#261F0E", - containerCapBackground: "#1C170A", foreground: "#DDAA22", foregroundSecondary: "#BB8E1A", foregroundTertiary: "#8A6814", - foregroundPlaceholder: "#634B0E", accent: "#FFCC44", - accentSecondary: "#CC9922", success: "#DDCC44", warning: "#FFE066", error: "#FF6633", info: "#EEBB55", border: "#5A4D2D", statusBarBackground: "#191508", - buttonBackground: "#3A3018", appHeaderBackground: "#1C170A", overlayBackground: "#0A0906", + surfaceBackground: "#261F0E", + surfaceHeaderBackground: "#1C170A", + elevatedBackground: "#3A3018", } }, { @@ -385,23 +379,21 @@ const newPalettes = [ desc: "Warm sunset terminals \u2014 inspired by sodium-vapor streetlights and desert radar stations.", colors: { background: "#0A0706", - containerBodyBackground: "#281710", - containerCapBackground: "#1E100C", foreground: "#FF7744", foregroundSecondary: "#CC5E36", foregroundTertiary: "#8F4226", - foregroundPlaceholder: "#66301B", accent: "#FF9966", - accentSecondary: "#DD6633", success: "#FFAA77", warning: "#FFCC66", error: "#FF4433", info: "#FFBB88", border: "#5A3A2D", statusBarBackground: "#190F0A", - buttonBackground: "#3A2418", appHeaderBackground: "#1E100C", overlayBackground: "#0A0706", + surfaceBackground: "#281710", + surfaceHeaderBackground: "#1E100C", + elevatedBackground: "#3A2418", } }, { @@ -409,23 +401,21 @@ const newPalettes = [ desc: "Arctic cryogenic readouts \u2014 inspired by ice-blue instrument clusters and polar research stations.", colors: { background: "#060809", - containerBodyBackground: "#0E1D28", - containerCapBackground: "#0A151E", foreground: "#88DDFF", foregroundSecondary: "#66AACC", foregroundTertiary: "#4A7A99", - foregroundPlaceholder: "#35586E", accent: "#AAEEFF", - accentSecondary: "#66BBDD", success: "#88EEBB", warning: "#CCDDAA", error: "#FF6633", info: "#BBEEFF", border: "#2D4A5A", statusBarBackground: "#0D1720", - buttonBackground: "#142D3E", appHeaderBackground: "#0A151E", overlayBackground: "#060809", + surfaceBackground: "#0E1D28", + surfaceHeaderBackground: "#0A151E", + elevatedBackground: "#142D3E", } }, ]; @@ -435,85 +425,87 @@ const existingPalettes = [ name: "Green", desc: "P1 phosphor", fg: "#33FF33", - colors: ["#060A07","#0E271C","#0A1B13", - "#33FF33","#27C227","#1F8F1F","#165A16", - "#66FF66","#00CC00", + colors: ["#060A07", + "#33FF33","#27C227","#1F8F1F", + "#66FF66", "#33FF33","#CCFF33","#FF6633","#33FFCC", "#2D5A2D", - "#0F2215","#145523","#0A1B13","#060A07"] + "#0F2215","#0A1B13","#060A07"] }, { name: "Amber", desc: "P3 phosphor", fg: "#FFAA00", - colors: ["#0A0706","#251710","#1E110E", - "#FFAA00","#CC8800","#8F6600","#664D00", - "#FFCC33","#CC9900", + colors: ["#0A0706", + "#FFAA00","#CC8800","#8F6600", + "#FFCC33", "#FFCC00","#FFE066","#FF6633","#FFD966", "#5A4A2D", - "#191613","#3A2A1D","#1E110E","#0A0706"] + "#191613","#1E110E","#0A0706"] }, { name: "Red", desc: "Military", fg: "#FF4444", - colors: ["#0A0606","#281112","#1E0F10", - "#FF4444","#CC3333","#8F2222","#661616", - "#FF6666","#CC4444", + colors: ["#0A0606", + "#FF4444","#CC3333","#8F2222", + "#FF6666", "#FF8080","#FFAA66","#FFFFFF","#FF9999", "#5A2D2D", - "#191313","#3A1F22","#1E0F10","#0A0606"] + "#191313","#1E0F10","#0A0606"] }, { name: "Violet", desc: "Sci-fi", fg: "#B275EF", - colors: ["#070509","#190F23","#110B18", - "#B275EF","#8C3BDC","#6528A3","#47236B", - "#C697F6","#7F1FDF", + colors: ["#070509", + "#B275EF","#8C3BDC","#6528A3", + "#C697F6", "#E4A567","#EF75B2","#A5F159","#7CB2E8", "#3F2659", - "#140D1B","#261537","#110B18","#070509"] + "#140D1B","#110B18","#070509"] }, { name: "Blue", desc: "VFD", fg: "#00AAFF", - colors: ["#060708","#0E1825","#0A121C", - "#00AAFF","#0088CC","#006699","#004D73", - "#33BBFF","#0099DD", + colors: ["#060708", + "#00AAFF","#0088CC","#006699", + "#33BBFF", "#33CCFF","#66CCFF","#FF6633","#99DDFF", "#2D4A5A", - "#0F1822","#14304A","#0A121C","#060708"] + "#0F1822","#0A121C","#060708"] }, { name: "White", desc: "P4 phosphor", fg: "#E8E8E8", - colors: ["#06070A","#111A2A","#0D131D", - "#E8E8E8","#B0B0B0","#787878","#505050", - "#FFFFFF","#C0C0C0", + colors: ["#06070A", + "#E8E8E8","#B0B0B0","#787878", + "#FFFFFF", "#C0FFC0","#FFE0A0","#FFA0A0","#A0D0FF", "#484848", - "#131619","#1D2535","#0D131D","#06070A"] + "#131619","#0D131D","#06070A"] }, ]; const colorLabels = [ - "background","containerBodyBackground","containerCapBackground", - "foreground","foregroundSecondary","foregroundTertiary","foregroundPlaceholder", - "accent","accentSecondary", + "background", + "foreground","foregroundSecondary","foregroundTertiary", + "accent", "success","warning","error","info", "border", - "statusBarBackground","buttonBackground","appHeaderBackground","overlayBackground" + "statusBarBackground","appHeaderBackground","overlayBackground", + "surfaceBackground","surfaceHeaderBackground","elevatedBackground" ]; const swatchGroups = [ - { title: "Backgrounds", keys: ["background","containerBodyBackground","containerCapBackground","buttonBackground","statusBarBackground","appHeaderBackground","overlayBackground"] }, - { title: "Foregrounds", keys: ["foreground","foregroundSecondary","foregroundTertiary","foregroundPlaceholder"] }, - { title: "Accents", keys: ["accent","accentSecondary"] }, - { title: "Semantic", keys: ["success","warning","error","info"] }, - { title: "UI Elements", keys: ["border"] }, + { title: "Palette — Backgrounds", keys: ["background","statusBarBackground","appHeaderBackground","overlayBackground"] }, + { title: "Palette — Foregrounds", keys: ["foreground","foregroundSecondary","foregroundTertiary"] }, + { title: "Palette — Accent", keys: ["accent"] }, + { title: "Palette — Semantic", keys: ["success","warning","error","info"] }, + { title: "Palette — UI Elements", keys: ["border"] }, + { title: "BlockPalette — Surfaces", keys: ["surfaceBackground","surfaceHeaderBackground","elevatedBackground"] }, ]; function camelToLabel(key) {