import Observation import SwiftUI enum AppAppearancePreference: String, CaseIterable, Identifiable { case system case light case dark static let storageKey = "appearance.preference" static var launchArgumentPreference: AppAppearancePreference? { let arguments = ProcessInfo.processInfo.arguments guard let flagIndex = arguments.firstIndex(of: "--openclaw-appearance") else { return nil } let valueIndex = arguments.index(after: flagIndex) guard arguments.indices.contains(valueIndex) else { return nil } return AppAppearancePreference(rawValue: arguments[valueIndex].lowercased()) } var id: String { self.rawValue } var label: String { switch self { case .system: "System" case .light: "Light" case .dark: "Dark" } } var systemImage: String { switch self { case .system: "circle.lefthalf.filled" case .light: "sun.max" case .dark: "moon.stars" } } var colorScheme: ColorScheme? { switch self { case .system: nil case .light: .light case .dark: .dark } } } @MainActor @Observable final class AppAppearanceModel { private(set) var preference: AppAppearancePreference init(userDefaults: UserDefaults = .standard) { let storedPreference = userDefaults.string(forKey: AppAppearancePreference.storageKey) .flatMap(AppAppearancePreference.init(rawValue:)) self.preference = AppAppearancePreference.launchArgumentPreference ?? storedPreference ?? .system if AppAppearancePreference.launchArgumentPreference != nil { userDefaults.set(self.preference.rawValue, forKey: AppAppearancePreference.storageKey) } } func select(_ preference: AppAppearancePreference, userDefaults: UserDefaults = .standard) { guard self.preference != preference else { return } userDefaults.set(preference.rawValue, forKey: AppAppearancePreference.storageKey) var transaction = Transaction() transaction.disablesAnimations = true withTransaction(transaction) { self.preference = preference } } } enum OpenClawBrand { // Accent fills stay dark enough for white content; foreground accents adapt // separately so small labels retain 4.5:1 contrast on dark surfaces and tinted pills. static let uiAccent = adaptiveUIColor(light: (183, 56, 51), dark: (198, 62, 56)) static let uiAccentForeground = adaptiveUIColor(light: (183, 56, 51), dark: (255, 107, 102)) static let uiAccentHot = adaptiveUIColor(light: (204, 75, 69), dark: (232, 92, 86)) static let uiAccentHotForeground = adaptiveUIColor(light: (166, 55, 50), dark: (255, 123, 115)) static let uiAccentPressed = adaptiveUIColor(light: (148, 40, 36), dark: (148, 40, 36)) static let uiTeal = adaptiveUIColor(light: (0, 196, 176), dark: (0, 196, 176)) static let uiVoid = adaptiveUIColor(light: (246, 247, 249), dark: (11, 12, 17)) static let uiObsidian = adaptiveUIColor(light: (255, 255, 255), dark: (19, 21, 28)) static let uiTextPrimary = adaptiveUIColor(light: (11, 12, 17), dark: (242, 239, 232)) static let uiTextSecondary = adaptiveUIColor(light: (90, 94, 110), dark: (168, 170, 191)) static let uiOK = adaptiveUIColor(light: (19, 122, 62), dark: (48, 209, 88)) static let uiWarn = adaptiveUIColor(light: (154, 87, 0), dark: (255, 214, 10)) static let uiDanger = adaptiveUIColor(light: (185, 28, 28), dark: (252, 165, 165)) static let uiInfo = adaptiveUIColor(light: (0, 91, 196), dark: (100, 168, 255)) static let accent = Color(uiColor: Self.uiAccent) static let accentForeground = Color(uiColor: Self.uiAccentForeground) static let accentHot = Color(uiColor: Self.uiAccentHot) static let accentHotForeground = Color(uiColor: Self.uiAccentHotForeground) static let accentPressed = Color(uiColor: Self.uiAccentPressed) static let teal = Color(uiColor: Self.uiTeal) static let void = Color(uiColor: Self.uiVoid) static let obsidian = Color(uiColor: Self.uiObsidian) static let textPrimary = Color(uiColor: Self.uiTextPrimary) static let textSecondary = Color(uiColor: Self.uiTextSecondary) static let danger = Color(uiColor: Self.uiDanger) static let ok = Color(uiColor: Self.uiOK) static let warn = Color(uiColor: Self.uiWarn) static let info = Color(uiColor: Self.uiInfo) static let graphite = void static let graphiteElevated = obsidian static var sheetBackground: LinearGradient { LinearGradient( colors: [ graphite, graphiteElevated.opacity(0.96), Color(uiColor: .systemBackground), ], startPoint: .topLeading, endPoint: .bottomTrailing) } private static func adaptiveUIColor( light: (red: CGFloat, green: CGFloat, blue: CGFloat), dark: (red: CGFloat, green: CGFloat, blue: CGFloat)) -> UIColor { UIColor { traits in let components = traits.userInterfaceStyle == .dark ? dark : light return UIColor( red: components.red / 255, green: components.green / 255, blue: components.blue / 255, alpha: 1) } } } extension View { func openClawSheetChrome() -> some View { self .tint(OpenClawBrand.accent) .background { OpenClawBrand.sheetBackground .ignoresSafeArea() } } }