// // AtlasTheme.swift // Zyquo Atlas // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // The user-overridable theme value type (Phase 4.2). A theme is a named color // palette + background + appearance, Codable so it imports/exports as a small // JSON file. The base tokens in ZyquoTheme seed the flagship "Atlas Light" // theme (Phase 4.1); the ThemeEngine swaps the active AtlasTheme live. Layout // and typography preferences live in LayoutSettings (separate from color // themes) so changing colors never disturbs the user's layout. // import SwiftUI extension Color { /// 0xRRGGBB → Color (sRGB). init(hex: UInt32) { self.init(.sRGB, red: Double((hex >> 16) & 0xFF) / 255, green: Double((hex >> 8) & 0xFF) / 255, blue: Double(hex & 0xFF) / 255, opacity: 1) } } enum ThemeAppearance: String, Codable, Hashable { case light, dark } /// The 13 semantic color slots (the flagship "Atlas Light" values are the /// Phase 4.1 base spec). struct AtlasPalette: Codable, Hashable { var background: UInt32 var surface: UInt32 var surfaceSecondary: UInt32 var accent: UInt32 var accentIndigo: UInt32 var accentSubtle: UInt32 var textPrimary: UInt32 var textSecondary: UInt32 var textTertiary: UInt32 var border: UInt32 var success: UInt32 var warning: UInt32 var danger: UInt32 } /// Background behind the chrome / new-tab page. struct BackgroundStyle: Codable, Hashable { enum Kind: String, Codable { case solid, gradient, image } var kind: Kind = .solid var colorA: UInt32 = 0xFAFBFC var colorB: UInt32 = 0xE6F5F3 /// Absolute path to a wallpaper image (image kind only). var imagePath: String? } struct AtlasTheme: Codable, Hashable, Identifiable { var id: String var name: String var appearance: ThemeAppearance var palette: AtlasPalette var background: BackgroundStyle // MARK: - Resolved SwiftUI colors (views read these) var backgroundColor: Color { Color(hex: palette.background) } var surface: Color { Color(hex: palette.surface) } var surfaceSecondary: Color { Color(hex: palette.surfaceSecondary) } var accent: Color { Color(hex: palette.accent) } var accentIndigo: Color { Color(hex: palette.accentIndigo) } var accentSubtle: Color { Color(hex: palette.accentSubtle) } var textPrimary: Color { Color(hex: palette.textPrimary) } var textSecondary: Color { Color(hex: palette.textSecondary) } var textTertiary: Color { Color(hex: palette.textTertiary) } var border: Color { Color(hex: palette.border) } var success: Color { Color(hex: palette.success) } var warning: Color { Color(hex: palette.warning) } var danger: Color { Color(hex: palette.danger) } var colorScheme: ColorScheme { appearance == .dark ? .dark : .light } /// The chrome/new-tab background as a SwiftUI view. @ViewBuilder var backgroundView: some View { switch background.kind { case .solid: backgroundColor case .gradient: LinearGradient(colors: [Color(hex: background.colorA), Color(hex: background.colorB)], startPoint: .top, endPoint: .bottom) case .image: if let path = background.imagePath, let img = NSImage(contentsOfFile: path) { Image(nsImage: img).resizable().aspectRatio(contentMode: .fill) } else { backgroundColor } } } } // MARK: - Layout & typography (separate from color themes) enum TabPosition: String, Codable, Hashable, CaseIterable { case top, left } enum Density: String, Codable, Hashable, CaseIterable { case compact, comfortable } /// User layout/typography preferences, persisted per profile and applied live. struct LayoutSettings: Codable, Hashable { var tabPosition: TabPosition = .top var density: Density = .comfortable var uiScale: Double = 1.0 // 0.85 … 1.30 var defaultPageZoom: Double = 1.0 var showBookmarksBar: Bool = false var translucency: Bool = false // Density-derived metrics. var toolbarHeight: CGFloat { density == .compact ? 38 : 44 } var tabBarHeight: CGFloat { density == .compact ? 30 : 36 } var verticalTabWidth: CGFloat { 220 } var rowSpacing: CGFloat { density == .compact ? ZyquoSpacing.xxs : ZyquoSpacing.xs } /// UI font at a base size, scaled by the user's uiScale. func font(_ base: Double, weight: Font.Weight = .regular) -> Font { .system(size: base * uiScale, weight: weight) } }