// // Theme.swift // SportsTime // // Central design system for colors, typography, spacing, and animations. // import SwiftUI // MARK: - App Theme Selection enum AppTheme: String, CaseIterable, Identifiable { case teal = "Teal" case orbit = "Orbit" case retro = "Retro" case clutch = "Clutch" case monochrome = "Monochrome" case sunset = "Sunset" case midnight = "Midnight" var id: String { rawValue } var displayName: String { rawValue } var description: String { switch self { case .teal: return "Cool cyan and teal tones" case .orbit: return "Bold navy and orange" case .retro: return "Classic columbia blue" case .clutch: return "Championship red and gold" case .monochrome: return "Clean grayscale aesthetic" case .sunset: return "Warm oranges and purples" case .midnight: return "Deep blues and gold" } } /// The alternate icon name in the asset catalog, or nil for the default (teal). var alternateIconName: String? { switch self { case .teal: return nil case .orbit: return "AppIcon-orbit" case .retro: return "AppIcon-retro" case .clutch: return "AppIcon-clutch" case .monochrome: return "AppIcon-monochrome" case .sunset: return "AppIcon-sunset" case .midnight: return "AppIcon-midnight" } } var previewColors: [Color] { switch self { case .teal: return [Color(hex: "4ECDC4"), Color(hex: "1A535C"), Color(hex: "FFE66D")] case .orbit: return [Color(hex: "EB6E1F"), Color(hex: "002D62"), Color(hex: "FFFFFF")] case .retro: return [Color(hex: "418FDE"), Color(hex: "C41E3A"), Color(hex: "FFFFFF")] case .clutch: return [Color(hex: "CE1141"), Color(hex: "FDB927"), Color(hex: "041E42")] case .monochrome: return [Color(hex: "808080"), Color(hex: "1A1A1A"), Color(hex: "FAFAFA")] case .sunset: return [Color(hex: "F97316"), Color(hex: "7C3AED"), Color(hex: "EC4899")] case .midnight: return [Color(hex: "3B82F6"), Color(hex: "1E3A5F"), Color(hex: "F59E0B")] } } } // MARK: - Theme Manager @Observable final class ThemeManager { static let shared = ThemeManager() var currentTheme: AppTheme { didSet { UserDefaults.standard.set(currentTheme.rawValue, forKey: "selectedTheme") updateAppIcon(for: currentTheme) } } private func updateAppIcon(for theme: AppTheme) { let iconName = theme.alternateIconName guard UIApplication.shared.alternateIconName != iconName else { return } UIApplication.shared.setAlternateIconName(iconName) } private init() { if let saved = UserDefaults.standard.string(forKey: "selectedTheme"), let theme = AppTheme(rawValue: saved) { self.currentTheme = theme } else { self.currentTheme = .teal } } } // MARK: - Appearance Mode enum AppearanceMode: String, CaseIterable, Identifiable { case system = "System" case light = "Light" case dark = "Dark" var id: String { rawValue } var displayName: String { rawValue } var iconName: String { switch self { case .system: return "circle.lefthalf.filled" case .light: return "sun.max.fill" case .dark: return "moon.fill" } } var description: String { switch self { case .system: return "Match device settings" case .light: return "Always use light mode" case .dark: return "Always use dark mode" } } /// Returns the ColorScheme to apply, or nil for system default var colorScheme: ColorScheme? { switch self { case .system: return nil case .light: return .light case .dark: return .dark } } } // MARK: - Appearance Manager @Observable final class AppearanceManager { static let shared = AppearanceManager() var currentMode: AppearanceMode { didSet { UserDefaults.standard.set(currentMode.rawValue, forKey: "appearanceMode") } } private init() { if let saved = UserDefaults.standard.string(forKey: "appearanceMode"), let mode = AppearanceMode(rawValue: saved) { self.currentMode = mode } else { self.currentMode = .system } } } // MARK: - Theme enum Theme { private static var current: AppTheme { ThemeManager.shared.currentTheme } // MARK: - Primary Accent Color static var warmOrange: Color { switch current { case .teal: return Color(hex: "4ECDC4") case .orbit: return Color(hex: "EB6E1F") case .retro: return Color(hex: "418FDE") case .clutch: return Color(hex: "CE1141") case .monochrome: return Color(hex: "808080") case .sunset: return Color(hex: "F97316") case .midnight: return Color(hex: "3B82F6") } } static var warmOrangeGlow: Color { warmOrange.opacity(0.7) } // MARK: - Secondary Accent Colors static var routeGold: Color { switch current { case .teal: return Color(hex: "FFE66D") case .orbit: return Color(hex: "002D62") case .retro: return Color(hex: "C41E3A") case .clutch: return Color(hex: "FDB927") case .monochrome: return Color(hex: "A0A0A0") case .sunset: return Color(hex: "7C3AED") case .midnight: return Color(hex: "F59E0B") } } static var routeAmber: Color { switch current { case .teal: return Color(hex: "FF6B6B") case .orbit: return Color(hex: "EB6E1F") case .retro: return Color(hex: "418FDE") case .clutch: return Color(hex: "CE1141") case .monochrome: return Color(hex: "505050") case .sunset: return Color(hex: "EC4899") case .midnight: return Color(hex: "60A5FA") } } // MARK: - Sport Colors (constant across themes) static let mlbRed = Color(hex: "E31937") static let nbaOrange = Color(hex: "F58426") static let nhlBlue = Color(hex: "003087") static let nflBrown = Color(hex: "8B5A2B") static let mlsGreen = Color(hex: "00A651") static let wnbaPurple = Color(hex: "FF6F20") // WNBA orange static let nwslTeal = Color(hex: "009688") // NWSL teal // MARK: - Dark Mode Colors static var darkBackground1: Color { switch current { case .teal: return Color(hex: "1A535C") case .orbit: return Color(hex: "002D62") case .retro: return Color(hex: "1A3A5C") case .clutch: return Color(hex: "041E42") case .monochrome: return Color(hex: "121212") case .sunset: return Color(hex: "1F1033") case .midnight: return Color(hex: "0F172A") } } static var darkBackground2: Color { switch current { case .teal: return Color(hex: "143F46") case .orbit: return Color(hex: "001A3A") case .retro: return Color(hex: "0F2840") case .clutch: return Color(hex: "020E1F") case .monochrome: return Color(hex: "0A0A0A") case .sunset: return Color(hex: "150A24") case .midnight: return Color(hex: "020617") } } static var darkCardBackground: Color { switch current { case .teal: return Color(hex: "1E5A64") case .orbit: return Color(hex: "0A3A6E") case .retro: return Color(hex: "234B6E") case .clutch: return Color(hex: "0A2847") case .monochrome: return Color(hex: "1C1C1C") case .sunset: return Color(hex: "2D1B4E") case .midnight: return Color(hex: "1E3A5F") } } static var darkCardBackgroundLight: Color { switch current { case .teal: return Color(hex: "2A6B75") case .orbit: return Color(hex: "154A7E") case .retro: return Color(hex: "2E5A7E") case .clutch: return Color(hex: "153557") case .monochrome: return Color(hex: "2A2A2A") case .sunset: return Color(hex: "3D2B5E") case .midnight: return Color(hex: "2A4A6F") } } static var darkSurfaceGlow: Color { warmOrange.opacity(0.15) } static var darkTextPrimary: Color { switch current { case .teal: return Color(hex: "F7FFF7") case .orbit: return Color(hex: "FFFFFF") case .retro: return Color(hex: "FFFFFF") case .clutch: return Color(hex: "FFFFFF") case .monochrome: return Color(hex: "FAFAFA") case .sunset: return Color(hex: "FFF7ED") case .midnight: return Color(hex: "F8FAFC") } } static var darkTextSecondary: Color { switch current { case .teal: return Color(hex: "B8E8E4") case .orbit: return Color(hex: "FFB380") case .retro: return Color(hex: "A8C8E8") case .clutch: return Color(hex: "FFD080") case .monochrome: return Color(hex: "D0D0D0") case .sunset: return Color(hex: "FED7AA") case .midnight: return Color(hex: "93C5FD") } } static var darkTextMuted: Color { switch current { case .teal: return Color(hex: "7FADA8") case .orbit: return Color(hex: "8090A0") case .retro: return Color(hex: "7898B8") case .clutch: return Color(hex: "8898A8") case .monochrome: return Color(hex: "707070") case .sunset: return Color(hex: "9D8AA8") case .midnight: return Color(hex: "64748B") } } // MARK: - Light Mode Colors static var lightBackground1: Color { switch current { case .teal: return Color(hex: "F7FFF7") case .orbit: return Color(hex: "FFF8F5") case .retro: return Color(hex: "F5F9FF") case .clutch: return Color(hex: "FFFAF5") case .monochrome: return Color(hex: "FAFAFA") case .sunset: return Color(hex: "FFFBF5") case .midnight: return Color(hex: "F8FAFC") } } static var lightBackground2: Color { switch current { case .teal: return Color(hex: "E8F8F5") case .orbit: return Color(hex: "FFF0E8") case .retro: return Color(hex: "E8F0FF") case .clutch: return Color(hex: "FFF0E8") case .monochrome: return Color(hex: "F0F0F0") case .sunset: return Color(hex: "FFF3E8") case .midnight: return Color(hex: "EFF6FF") } } static var lightCardBackground: Color { .white } static var lightCardBackgroundElevated: Color { lightBackground1 } static var lightSurfaceBorder: Color { warmOrange.opacity(0.3) } static var lightTextPrimary: Color { switch current { case .teal: return Color(hex: "1A535C") case .orbit: return Color(hex: "002D62") case .retro: return Color(hex: "1A3A5C") case .clutch: return Color(hex: "041E42") case .monochrome: return Color(hex: "1A1A1A") case .sunset: return Color(hex: "431407") case .midnight: return Color(hex: "1E3A5F") } } static var lightTextSecondary: Color { switch current { case .teal: return Color(hex: "2A6B75") case .orbit: return Color(hex: "1A4A7A") case .retro: return Color(hex: "2A5A7C") case .clutch: return Color(hex: "1A3A5A") case .monochrome: return Color(hex: "404040") case .sunset: return Color(hex: "7C2D12") case .midnight: return Color(hex: "2A4A6F") } } static var lightTextMuted: Color { switch current { case .teal: return Color(hex: "5A9A94") case .orbit: return Color(hex: "5A7A9A") case .retro: return Color(hex: "5A8AAA") case .clutch: return Color(hex: "6A7A8A") case .monochrome: return Color(hex: "707070") case .sunset: return Color(hex: "9A6A5A") case .midnight: return Color(hex: "64748B") } } // MARK: - Adaptive Gradients static func backgroundGradient(_ colorScheme: ColorScheme) -> LinearGradient { colorScheme == .dark ? LinearGradient(colors: [darkBackground1, darkBackground2], startPoint: .top, endPoint: .bottom) : LinearGradient(colors: [lightBackground1, lightBackground2], startPoint: .top, endPoint: .bottom) } // MARK: - Adaptive Colors static func cardBackground(_ colorScheme: ColorScheme) -> Color { colorScheme == .dark ? darkCardBackground : lightCardBackground } static func cardBackgroundElevated(_ colorScheme: ColorScheme) -> Color { colorScheme == .dark ? darkCardBackgroundLight : lightCardBackgroundElevated } static func textPrimary(_ colorScheme: ColorScheme) -> Color { colorScheme == .dark ? darkTextPrimary : lightTextPrimary } static func textSecondary(_ colorScheme: ColorScheme) -> Color { colorScheme == .dark ? darkTextSecondary : lightTextSecondary } static func textMuted(_ colorScheme: ColorScheme) -> Color { colorScheme == .dark ? darkTextMuted : lightTextMuted } static func surfaceGlow(_ colorScheme: ColorScheme) -> Color { colorScheme == .dark ? darkSurfaceGlow : lightSurfaceBorder } static func cardShadow(_ colorScheme: ColorScheme) -> Color { colorScheme == .dark ? Color.black.opacity(0.3) : Color.black.opacity(0.08) } // MARK: - Spacing enum Spacing { static let xxs: CGFloat = 4 static let xs: CGFloat = 8 static let sm: CGFloat = 12 static let md: CGFloat = 16 static let lg: CGFloat = 20 static let xl: CGFloat = 24 static let xxl: CGFloat = 32 } // MARK: - Corner Radius enum CornerRadius { static let small: CGFloat = 8 static let medium: CGFloat = 12 static let large: CGFloat = 16 static let xlarge: CGFloat = 20 } // MARK: - Animation enum Animation { static let springResponse: Double = 0.3 static let springDamping: Double = 0.7 static let staggerDelay: Double = 0.1 static let routeDrawDuration: Double = 2.0 static var spring: SwiftUI.Animation { .spring(response: springResponse, dampingFraction: springDamping) } static var gentleSpring: SwiftUI.Animation { .spring(response: 0.5, dampingFraction: 0.8) } } } // MARK: - Color Hex Extension extension Color { init(hex: String) { let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) var int: UInt64 = 0 Scanner(string: hex).scanHexInt64(&int) let a, r, g, b: UInt64 switch hex.count { case 3: // RGB (12-bit) (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) case 6: // RGB (24-bit) (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) case 8: // ARGB (32-bit) (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) default: (a, r, g, b) = (255, 0, 0, 0) } self.init( .sRGB, red: Double(r) / 255, green: Double(g) / 255, blue: Double(b) / 255, opacity: Double(a) / 255 ) } } // MARK: - Environment Key for Theme private struct ColorSchemeKey: EnvironmentKey { static let defaultValue: ColorScheme = .light } extension EnvironmentValues { var themeColorScheme: ColorScheme { get { self[ColorSchemeKey.self] } set { self[ColorSchemeKey.self] = newValue } } }