diff --git a/Sources/TUIkit/Styling/Palettes/BluePalette.swift b/Sources/TUIkit/Styling/Palettes/BluePalette.swift new file mode 100644 index 0000000..5c320c3 --- /dev/null +++ b/Sources/TUIkit/Styling/Palettes/BluePalette.swift @@ -0,0 +1,60 @@ +// BluePalette.swift +// TUIkit — Terminal UI Framework for Swift +// +// Created by LAYERED.work +// CC BY-NC-SA 4.0 + +/// Blue VFD terminal palette. +/// +/// 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 let id = "blue" + public let name = "Blue" + + // Background hierarchy + public let background = Color.hex(0x060708) // App background (darkest) + public let backgroundSecondary = Color.hex(0x0E1825) // Container body background (brighter) + public let backgroundTertiary = Color.hex(0x0A121C) // Header/footer background + + // 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 + + // Accent colors + 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 + public let warning = Color.hex(0x66CCFF) // Light cyan + public let error = Color.hex(0xFF6633) // Orange-red (contrast) + public let info = Color.hex(0x99DDFF) // Pale blue + + // UI elements + public let border = Color.hex(0x2D4A5A) // Subtle blue border + public let borderFocused = Color.hex(0x00AAFF) // Bright when focused + public let selection = Color.hex(0x33BBFF) // Bright blue for selection text + public let selectionBackground = Color.hex(0x1A3A4D) // Dark blue for selection bar bg + + // Status bar + public let statusBarBackground = Color.hex(0x0F1822) // Dark blue for status bar + public let statusBarForeground = Color.hex(0x0099EE) // Slightly dimmer than primary foreground + public let statusBarHighlight = Color.hex(0x33BBFF) + + // Container colors for block appearance + public var containerBackground: Color { backgroundSecondary } // Body + public var containerHeaderBackground: Color { backgroundTertiary } // Header/footer + public var buttonBackground: Color { Color.hex(0x14304A) } // Lighter blue for buttons + + public init() {} +} + +// MARK: - Convenience Accessors + +extension Palette where Self == BluePalette { + /// Blue VFD terminal palette. + public static var blue: BluePalette { BluePalette() } +} diff --git a/Sources/TUIkit/Styling/Palettes/GeneratedPalette.swift b/Sources/TUIkit/Styling/Palettes/GeneratedPalette.swift deleted file mode 100644 index 3ab8ab3..0000000 --- a/Sources/TUIkit/Styling/Palettes/GeneratedPalette.swift +++ /dev/null @@ -1,164 +0,0 @@ -// -// GeneratedPalette.swift -// TUIkit -// -// A palette that generates its entire color scheme from a single base hue. -// - -/// A palette that generates its entire color scheme from a single base hue. -/// -/// All colors are derived algorithmically using HSL transformations. -/// This allows creating new palettes with a single line of code. -/// -/// # How it works -/// -/// Given a base hue (0-360), the palette generates: -/// - **Backgrounds**: very low lightness, subtle saturation tinted toward the hue -/// - **Foregrounds**: high lightness with full saturation at the base hue -/// - **Accents**: slightly lighter/shifted versions of the foreground -/// - **Semantic colors**: hue-shifted variants (success = +120, warning = +60, error = +180, info = -60) -/// - **UI elements**: mid-range lightness/saturation for borders, selection, status bar -/// -/// # Example -/// -/// ```swift -/// // Create a violet palette -/// let violet = GeneratedPalette(name: "Violet", hue: 270) -/// -/// // Create a teal palette -/// let teal = GeneratedPalette(name: "Teal", hue: 180) -/// ``` -public struct GeneratedPalette: Palette, Sendable { - public let id: String - public let name: String - - // Background hierarchy - public let background: Color - public let backgroundSecondary: Color - public let backgroundTertiary: Color - - // Foreground hierarchy - public let foreground: Color - public let foregroundSecondary: Color - public let foregroundTertiary: Color - - // Accents - public let accent: Color - public let accentSecondary: Color - - // Semantic - public let success: Color - public let warning: Color - public let error: Color - public let info: Color - - // UI elements - public let border: Color - public let borderFocused: Color - public let selection: Color - public let selectionBackground: Color - - // Status bar - public let statusBarBackground: Color - public let statusBarForeground: Color - public let statusBarHighlight: Color - - // Container (block appearance) - public let containerBackground: Color - public let containerHeaderBackground: Color - public let buttonBackground: Color - - /// Creates a generated palette from a single base hue. - /// - /// - Parameters: - /// - name: The display name for the palette. - /// - hue: The base hue (0-360). Examples: red=0, green=120, blue=240, violet=270. - /// - saturation: The base saturation (0-100, default: 100). Lower values produce more muted palettes. - public init(name: String, hue: Double, saturation: Double = 100) { - self.id = "generated-\(name.lowercased())" - self.name = name - - let baseSaturation = min(100, max(0, saturation)) - - // --- Backgrounds: very dark, subtly tinted --- - // App background — near black with a hint of the hue - self.background = Color.hsl(hue, baseSaturation * 0.30, 3) - // Container body — slightly brighter - self.backgroundSecondary = Color.hsl(hue, baseSaturation * 0.40, 10) - // Header/footer — between app and body - self.backgroundTertiary = Color.hsl(hue, baseSaturation * 0.35, 7) - - // --- Foregrounds: bright, saturated text --- - self.foreground = Color.hsl(hue, baseSaturation * 0.80, 70) - self.foregroundSecondary = Color.hsl(hue, baseSaturation * 0.70, 55) - self.foregroundTertiary = Color.hsl(hue, baseSaturation * 0.60, 40) - - // --- Accents: lighter/brighter variant --- - self.accent = Color.hsl(hue, baseSaturation * 0.85, 78) - self.accentSecondary = Color.hsl(hue, baseSaturation * 0.75, 50) - - // --- Semantic: hue-shifted from base --- - // success = base + 120° (toward green family) - let successHue = Self.wrapHue(hue + 120) - self.success = Color.hsl(successHue, baseSaturation * 0.70, 65) - - // warning = base + 60° (toward yellow family) - let warningHue = Self.wrapHue(hue + 60) - self.warning = Color.hsl(warningHue, baseSaturation * 0.80, 70) - - // error = base + 180° (complementary) - let errorHue = Self.wrapHue(hue + 180) - self.error = Color.hsl(errorHue, baseSaturation * 0.85, 65) - - // info = base − 60° (analogous cool side) - let infoHue = Self.wrapHue(hue - 60) - self.info = Color.hsl(infoHue, baseSaturation * 0.70, 70) - - // --- UI elements --- - self.border = Color.hsl(hue, baseSaturation * 0.40, 25) - self.borderFocused = Color.hsl(hue, baseSaturation * 0.80, 70) - self.selection = Color.hsl(hue, baseSaturation * 0.85, 78) - self.selectionBackground = Color.hsl(hue, baseSaturation * 0.50, 18) - - // --- Status bar --- - self.statusBarBackground = Color.hsl(hue, baseSaturation * 0.35, 8) - self.statusBarForeground = Color.hsl(hue, baseSaturation * 0.75, 65) - self.statusBarHighlight = Color.hsl(hue, baseSaturation * 0.85, 78) - - // --- Container (block appearance) --- - self.containerBackground = Color.hsl(hue, baseSaturation * 0.40, 10) - self.containerHeaderBackground = Color.hsl(hue, baseSaturation * 0.35, 7) - self.buttonBackground = Color.hsl(hue, baseSaturation * 0.45, 15) - } - - /// Wraps a hue value to the 0-360 range. - private static func wrapHue(_ hue: Double) -> Double { - var wrapped = hue.truncatingRemainder(dividingBy: 360) - if wrapped < 0 { wrapped += 360 } - return wrapped - } - - // MARK: - Preset Hues - - /// Predefined hue values for common generated palettes. - enum Hue { - /// Green hue (120°). - static let green: Double = 120 - /// Violet hue (270°). - static let violet: Double = 270 - } - - // MARK: - Presets - - /// A green generated palette — for direct comparison with GreenPalette. - public static let green = Self(name: "Gen. Green", hue: Hue.green) - /// A violet generated palette. - public static let violet = Self(name: "Violet", hue: Hue.violet) -} - -// MARK: - Convenience Accessors - -extension Palette where Self == GeneratedPalette { - /// Violet generated palette (hue 270°). - public static var violet: GeneratedPalette { GeneratedPalette.violet } -} diff --git a/Sources/TUIkit/Styling/Palettes/VioletPalette.swift b/Sources/TUIkit/Styling/Palettes/VioletPalette.swift new file mode 100644 index 0000000..7a8395a --- /dev/null +++ b/Sources/TUIkit/Styling/Palettes/VioletPalette.swift @@ -0,0 +1,108 @@ +// VioletPalette.swift +// TUIkit — Terminal UI Framework for Swift +// +// Created by LAYERED.work +// CC BY-NC-SA 4.0 + +/// Violet terminal palette. +/// +/// 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 let id = "violet" + public let name = "Violet" + + /// The base hue used to generate all palette colors. + private static let baseHue: Double = 270 + + // Background hierarchy + public let background: Color + public let backgroundSecondary: Color + public let backgroundTertiary: Color + + // Violet text hierarchy + public let foreground: Color + public let foregroundSecondary: Color + public let foregroundTertiary: Color + + // Accent colors + public let accent: Color + public let accentSecondary: Color + + // Semantic colors + public let success: Color + public let warning: Color + public let error: Color + public let info: Color + + // UI elements + public let border: Color + public let borderFocused: Color + public let selection: Color + public let selectionBackground: Color + + // Status bar + public let statusBarBackground: Color + public let statusBarForeground: Color + public let statusBarHighlight: Color + + // Container colors for block appearance + public let containerBackground: Color + public let containerHeaderBackground: Color + public let buttonBackground: Color + + public init() { + let hue = Self.baseHue + + // Backgrounds: very dark, subtly tinted + self.background = Color.hsl(hue, 30, 3) + self.backgroundSecondary = Color.hsl(hue, 40, 10) + self.backgroundTertiary = 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) + + // Accents: 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) + self.warning = Color.hsl(Self.wrapHue(hue + 60), 80, 70) + self.error = Color.hsl(Self.wrapHue(hue + 180), 85, 65) + self.info = Color.hsl(Self.wrapHue(hue - 60), 70, 70) + + // UI elements + self.border = Color.hsl(hue, 40, 25) + self.borderFocused = Color.hsl(hue, 80, 70) + self.selection = Color.hsl(hue, 85, 78) + self.selectionBackground = Color.hsl(hue, 50, 18) + + // Status bar + self.statusBarBackground = Color.hsl(hue, 35, 8) + self.statusBarForeground = Color.hsl(hue, 75, 65) + self.statusBarHighlight = Color.hsl(hue, 85, 78) + + // Container (block appearance) + self.containerBackground = Color.hsl(hue, 40, 10) + self.containerHeaderBackground = Color.hsl(hue, 35, 7) + self.buttonBackground = Color.hsl(hue, 45, 15) + } + + /// Wraps a hue value to the 0–360 range. + private static func wrapHue(_ hue: Double) -> Double { + var wrapped = hue.truncatingRemainder(dividingBy: 360) + if wrapped < 0 { wrapped += 360 } + return wrapped + } +} + +// MARK: - Convenience Accessors + +extension Palette where Self == VioletPalette { + /// Violet terminal palette. + public static var violet: VioletPalette { VioletPalette() } +} diff --git a/Sources/TUIkit/Styling/Theme.swift b/Sources/TUIkit/Styling/Theme.swift index fcc3d17..8f20116 100644 --- a/Sources/TUIkit/Styling/Theme.swift +++ b/Sources/TUIkit/Styling/Theme.swift @@ -174,15 +174,15 @@ extension EnvironmentValues { struct PaletteRegistry { /// All available palettes in cycling order. /// - /// Order: Green → Gen. Green → Amber → White → Red → NCurses → Violet (generated) + /// Order: Green → Amber → Red → Violet → Blue → White → NCurses static let all: [any Palette] = [ GreenPalette(), - GeneratedPalette.green, AmberPalette(), - WhitePalette(), RedPalette(), + VioletPalette(), + BluePalette(), + WhitePalette(), NCursesPalette(), - GeneratedPalette.violet, ] /// Finds a palette by ID. diff --git a/Tests/TUIkitTests/GeneratedPaletteTests.swift b/Tests/TUIkitTests/GeneratedPaletteTests.swift deleted file mode 100644 index c402b8d..0000000 --- a/Tests/TUIkitTests/GeneratedPaletteTests.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// GeneratedPaletteTests.swift -// TUIkit -// -// Tests for GeneratedPalette: hue-based palette generation. -// - -import Testing - -@testable import TUIkit - -@Suite("Generated Palette Tests") -struct GeneratedPaletteTests { - - @Test("Generated palette creates ID from name") - func generatedId() { - let palette = GeneratedPalette(name: "Violet", hue: 270) - #expect(palette.id == "generated-violet") - #expect(palette.name == "Violet") - } - - @Test("Generated palette with different hues produces different colors") - func differentHues() { - let green = GeneratedPalette(name: "Green", hue: 120) - let violet = GeneratedPalette(name: "Violet", hue: 270) - #expect(green.foreground != violet.foreground) - #expect(green.accent != violet.accent) - } - - @Test("Generated palette clamps saturation to 0-100") - func saturationClamping() { - // Should not crash with out-of-range values - let oversaturated = GeneratedPalette(name: "Over", hue: 120, saturation: 200) - let undersaturated = GeneratedPalette(name: "Under", hue: 120, saturation: -50) - #expect(oversaturated.foreground != undersaturated.foreground) - } - - @Test("Generated palette preset hue constants") - func presetHues() { - #expect(GeneratedPalette.Hue.green == 120) - #expect(GeneratedPalette.Hue.violet == 270) - } - - @Test("Generated palette presets exist") - func presets() { - let green = GeneratedPalette.green - #expect(green.name == "Gen. Green") - #expect(green.id == "generated-gen. green") - - let violet = GeneratedPalette.violet - #expect(violet.name == "Violet") - #expect(violet.id == "generated-violet") - } - - @Test("Generated palette has distinct background hierarchy") - func generatedBackgrounds() { - let palette = GeneratedPalette(name: "Test", hue: 180) - #expect(palette.background != palette.backgroundSecondary) - #expect(palette.backgroundSecondary != palette.backgroundTertiary) - } - -} diff --git a/Tests/TUIkitTests/PaletteRegistryTests.swift b/Tests/TUIkitTests/PaletteRegistryTests.swift index 81532b8..a7510ca 100644 --- a/Tests/TUIkitTests/PaletteRegistryTests.swift +++ b/Tests/TUIkitTests/PaletteRegistryTests.swift @@ -14,19 +14,19 @@ struct PaletteRegistryTests { @Test("Registry contains all predefined palettes") func registryCount() { - // Green, Gen. Green, Amber, White, Red, NCurses, Violet = 7 + // Green, Amber, Red, Violet, Blue, White, NCurses = 7 #expect(PaletteRegistry.all.count == 7) } - @Test("Registry cycling order starts with green") + @Test("Registry cycling order follows color spectrum") func registryCyclingOrder() { #expect(PaletteRegistry.all[0].id == "green") - #expect(PaletteRegistry.all[1].id == "generated-gen. green") - #expect(PaletteRegistry.all[2].id == "amber") - #expect(PaletteRegistry.all[3].id == "white") - #expect(PaletteRegistry.all[4].id == "red") - #expect(PaletteRegistry.all[5].id == "ncurses") - #expect(PaletteRegistry.all[6].id == "generated-violet") + #expect(PaletteRegistry.all[1].id == "amber") + #expect(PaletteRegistry.all[2].id == "red") + #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 46da79c..667db1a 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, White, Red, NCurses. +// Tests for all predefined palette structs: Green, Amber, Red, Violet, Blue, White, NCurses. // import Testing @@ -97,6 +97,64 @@ struct RedPaletteTests { } } +// MARK: - Violet Palette Tests + +@Suite("Violet Palette Tests") +struct VioletPaletteTests { + + @Test("Violet palette has correct identity") + func violetIdentity() { + let palette = VioletPalette() + #expect(palette.id == "violet") + #expect(palette.name == "Violet") + } + + @Test("Violet palette has distinct background hierarchy") + func violetBackgrounds() { + let palette = VioletPalette() + #expect(palette.background != palette.backgroundSecondary) + #expect(palette.backgroundSecondary != palette.backgroundTertiary) + #expect(palette.background != palette.backgroundTertiary) + } + + @Test("Violet palette colors differ from green palette") + func violetDiffersFromGreen() { + let violet = VioletPalette() + let green = GreenPalette() + #expect(violet.foreground != green.foreground) + #expect(violet.accent != green.accent) + } +} + +// MARK: - Blue Palette Tests + +@Suite("Blue Palette Tests") +struct BluePaletteTests { + + @Test("Blue palette has correct identity") + func blueIdentity() { + let palette = BluePalette() + #expect(palette.id == "blue") + #expect(palette.name == "Blue") + } + + @Test("Blue palette has distinct background hierarchy") + func blueBackgrounds() { + let palette = BluePalette() + #expect(palette.background != palette.backgroundSecondary) + #expect(palette.backgroundSecondary != palette.backgroundTertiary) + #expect(palette.background != palette.backgroundTertiary) + } + + @Test("Blue palette colors differ from violet palette") + func blueDiffersFromViolet() { + let blue = BluePalette() + let violet = VioletPalette() + #expect(blue.foreground != violet.foreground) + #expect(blue.accent != violet.accent) + } +} + // MARK: - NCurses Palette Tests @Suite("NCurses Palette Tests") diff --git a/docs/app/components/ThemeProvider.tsx b/docs/app/components/ThemeProvider.tsx index 9a9a120..54cb330 100644 --- a/docs/app/components/ThemeProvider.tsx +++ b/docs/app/components/ThemeProvider.tsx @@ -4,12 +4,13 @@ import { createContext, useCallback, useContext, + useEffect, useState, type ReactNode, } from "react"; /** Available phosphor themes matching TUIkit's built-in palettes. */ -export const themes = ["green", "amber", "white", "red"] as const; +export const themes = ["green", "amber", "red", "violet", "blue", "white"] as const; export type Theme = (typeof themes)[number]; interface ThemeContextValue { @@ -18,7 +19,7 @@ interface ThemeContextValue { } const ThemeContext = createContext({ - theme: "amber", + theme: "green", setTheme: () => {}, }); @@ -33,13 +34,19 @@ function getInitialTheme(): Theme { const attr = document.documentElement.getAttribute("data-theme") as Theme | null; if (attr && themes.includes(attr)) return attr; } - return "amber"; + return "green"; } /** Provides theme state and applies it to the document root via data-theme attribute. */ export function ThemeProvider({ children }: { children: ReactNode }) { const [theme, setThemeState] = useState(getInitialTheme); + /** Sync React state with the theme the blocking script already applied to the DOM. */ + useEffect(() => { + const actual = getInitialTheme(); + setThemeState(actual); + }, []); + const setTheme = useCallback((next: Theme) => { setThemeState(next); document.documentElement.setAttribute("data-theme", next); diff --git a/docs/app/components/ThemeSwitcher.tsx b/docs/app/components/ThemeSwitcher.tsx index 9a90fb9..657339a 100644 --- a/docs/app/components/ThemeSwitcher.tsx +++ b/docs/app/components/ThemeSwitcher.tsx @@ -2,20 +2,24 @@ import { useTheme, themes, type Theme } from "./ThemeProvider"; -/** Color dot for each theme used in the switcher. */ +/** Color dot for each theme, matching Swift palette foreground colors. */ const themeColors: Record = { green: "#33ff33", - amber: "#ffbf00", - white: "#e4e4e7", - red: "#ff3333", + amber: "#ffaa00", + red: "#ff4444", + violet: "#bb77ff", + blue: "#00aaff", + white: "#e8e8e8", }; /** Theme labels for accessibility. */ const themeLabels: Record = { green: "Green", amber: "Amber", - white: "White", red: "Red", + violet: "Violet", + blue: "Blue", + white: "White", }; /** Compact theme switcher with colored dots and active indicator. */ diff --git a/docs/app/globals.css b/docs/app/globals.css index 97bd8f0..844dba3 100644 --- a/docs/app/globals.css +++ b/docs/app/globals.css @@ -9,61 +9,109 @@ font-display: swap; } -/* Theme: Amber (default) */ -:root, -[data-theme="amber"] { - --background: #0a0a0a; - --foreground: #e8dcc2; - --accent: #fbbf24; - --accent-secondary: #f59e0b; - --accent-glow: 251, 191, 36; - --accent-glow-secondary: 245, 158, 11; - --accent-glow-tertiary: 217, 119, 6; - --headline-color: #ffbf00; - --headline-glow: 255, 191, 0; - --muted: #ad9a6e; - --border: #27272a; - --card: #111113; - --card-hover: #18181b; -} +/* Themes: Green → Amber → Red → Violet → Blue → White */ -/* Theme: Green */ +/* Theme: Green (default) */ +:root, [data-theme="green"] { - --foreground: #c2e8d4; - --accent: #6ee7b7; - --accent-secondary: #34d399; - --accent-glow: 110, 231, 183; - --accent-glow-secondary: 52, 211, 153; - --accent-glow-tertiary: 16, 185, 129; + --background: #060a07; + --foreground: #33ff33; + --accent: #66ff66; + --accent-secondary: #00cc00; + --accent-glow: 102, 255, 102; + --accent-glow-secondary: 0, 204, 0; + --accent-glow-tertiary: 31, 143, 31; --headline-color: #33ff33; --headline-glow: 51, 255, 51; - --muted: #7aad94; + --muted: #1f8f1f; + --border: #2d5a2d; + --card: #0e271c; + --card-hover: #0a1b13; } -/* Theme: White */ -[data-theme="white"] { - --foreground: #d8d8da; - --accent: #e4e4e7; - --accent-secondary: #a1a1aa; - --accent-glow: 228, 228, 231; - --accent-glow-secondary: 161, 161, 170; - --accent-glow-tertiary: 113, 113, 122; - --headline-color: #f0f0f0; - --headline-glow: 240, 240, 240; - --muted: #8a8a90; +/* Theme: Amber */ +[data-theme="amber"] { + --background: #0a0706; + --foreground: #ffaa00; + --accent: #ffcc33; + --accent-secondary: #cc9900; + --accent-glow: 255, 204, 51; + --accent-glow-secondary: 204, 153, 0; + --accent-glow-tertiary: 143, 102, 0; + --headline-color: #ffaa00; + --headline-glow: 255, 170, 0; + --muted: #8f6600; + --border: #5a4a2d; + --card: #251710; + --card-hover: #1e110e; } /* Theme: Red */ [data-theme="red"] { - --foreground: #e8c8c8; - --accent: #f87171; - --accent-secondary: #ef4444; - --accent-glow: 248, 113, 113; - --accent-glow-secondary: 239, 68, 68; - --accent-glow-tertiary: 220, 38, 38; - --headline-color: #ff3333; - --headline-glow: 255, 51, 51; - --muted: #ad7a7a; + --background: #0a0606; + --foreground: #ff4444; + --accent: #ff6666; + --accent-secondary: #cc4444; + --accent-glow: 255, 68, 68; + --accent-glow-secondary: 204, 51, 51; + --accent-glow-tertiary: 143, 34, 34; + --headline-color: #ff4444; + --headline-glow: 255, 68, 68; + --muted: #8f2222; + --border: #5a2d2d; + --card: #281112; + --card-hover: #1e0f10; +} + +/* Theme: Violet */ +[data-theme="violet"] { + --background: #08060a; + --foreground: #bb77ff; + --accent: #cc99ff; + --accent-secondary: #9944dd; + --accent-glow: 204, 153, 255; + --accent-glow-secondary: 153, 68, 221; + --accent-glow-tertiary: 119, 68, 170; + --headline-color: #bb77ff; + --headline-glow: 187, 119, 255; + --muted: #7744aa; + --border: #4a2d5a; + --card: #1a1028; + --card-hover: #120c1e; +} + +/* Theme: Blue (VFD) */ +[data-theme="blue"] { + --background: #060708; + --foreground: #00aaff; + --accent: #33bbff; + --accent-secondary: #0099dd; + --accent-glow: 51, 187, 255; + --accent-glow-secondary: 0, 153, 221; + --accent-glow-tertiary: 0, 102, 153; + --headline-color: #00aaff; + --headline-glow: 0, 170, 255; + --muted: #006699; + --border: #2d4a5a; + --card: #0e1825; + --card-hover: #0a121c; +} + +/* Theme: White */ +[data-theme="white"] { + --background: #06070a; + --foreground: #e8e8e8; + --accent: #ffffff; + --accent-secondary: #c0c0c0; + --accent-glow: 255, 255, 255; + --accent-glow-secondary: 192, 192, 192; + --accent-glow-tertiary: 120, 120, 120; + --headline-color: #e8e8e8; + --headline-glow: 232, 232, 232; + --muted: #787878; + --border: #484848; + --card: #111a2a; + --card-hover: #0d131d; } @theme inline { diff --git a/docs/app/layout.tsx b/docs/app/layout.tsx index 0e7430b..7fcd277 100644 --- a/docs/app/layout.tsx +++ b/docs/app/layout.tsx @@ -36,13 +36,13 @@ export default function RootLayout({ (function() { try { var stored = localStorage.getItem('tuikit-theme'); - if (stored && ['green','amber','white','red'].includes(stored)) { + if (stored && ['green','amber','red','violet','blue','white'].includes(stored)) { document.documentElement.setAttribute('data-theme', stored); } else { - document.documentElement.setAttribute('data-theme', 'amber'); + document.documentElement.setAttribute('data-theme', 'green'); } } catch(e) { - document.documentElement.setAttribute('data-theme', 'amber'); + document.documentElement.setAttribute('data-theme', 'green'); } })(); `; diff --git a/docs/public/tuikit-logo.png b/docs/public/tuikit-logo.png index 13014cf..8ec91c8 100644 Binary files a/docs/public/tuikit-logo.png and b/docs/public/tuikit-logo.png differ