diff --git a/SportsTime/Assets.xcassets/AppIcon.appiconset/Contents.json b/SportsTime/Assets.xcassets/AppIcon.appiconset/Contents.json index 2305880..c68da6c 100644 --- a/SportsTime/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/SportsTime/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "icon.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/SportsTime/Assets.xcassets/AppIcon.appiconset/icon.png b/SportsTime/Assets.xcassets/AppIcon.appiconset/icon.png new file mode 100644 index 0000000..6911b8d Binary files /dev/null and b/SportsTime/Assets.xcassets/AppIcon.appiconset/icon.png differ diff --git a/SportsTime/Core/Design/UIDesignStyle.swift b/SportsTime/Core/Design/UIDesignStyle.swift index 2f71e84..fad9b37 100644 --- a/SportsTime/Core/Design/UIDesignStyle.swift +++ b/SportsTime/Core/Design/UIDesignStyle.swift @@ -2,43 +2,16 @@ // UIDesignStyle.swift // SportsTime // -// 22 distinctive aesthetic variants for the home screen. +// UI design style variants for the home screen. // import SwiftUI /// Available UI design aesthetics for the home screen enum UIDesignStyle: String, CaseIterable, Identifiable, Codable { - // Default case classic = "Classic" case classicAnimated = "Classic Animated" - // Original experimental aesthetics - case brutalist = "Brutalist" - case luxuryEditorial = "Luxury Editorial" - case retroFuturism = "Retro-Futurism" - case glassmorphism = "Glassmorphism" - case neoBrutalist = "Neo-Brutalist" - case organic = "Organic" - case maximalistChaos = "Maximalist" - case swissModernist = "Swiss Modern" - case playful = "Playful" - case artDeco = "Art Deco" - case darkIndustrial = "Industrial" - case softPastel = "Soft Pastel" - - // Polished app-inspired aesthetics - case flighty = "Flighty" - case seatGeek = "SeatGeek" - case appleMaps = "Apple Maps" - case things3 = "Things 3" - case airbnb = "Airbnb" - case spotify = "Spotify" - case nikeRunClub = "Nike Run Club" - case fantastical = "Fantastical" - case strava = "Strava" - case carrotWeather = "Carrot Weather" - var id: String { rawValue } var description: String { @@ -47,50 +20,6 @@ enum UIDesignStyle: String, CaseIterable, Identifiable, Codable { return "The original SportsTime design" case .classicAnimated: return "Classic with animated sports backgrounds" - case .brutalist: - return "Raw, unpolished anti-design rebellion" - case .luxuryEditorial: - return "Magazine-quality dramatic typography" - case .retroFuturism: - return "80s sci-fi meets modern sports tech" - case .glassmorphism: - return "Frosted glass, flowing ethereal shapes" - case .neoBrutalist: - return "Bold blocks, harsh shadows, high contrast" - case .organic: - return "Soft curves, earthy tones, breathing life" - case .maximalistChaos: - return "Dense, layered, gloriously overwhelming" - case .swissModernist: - return "Grid-obsessed clinical precision" - case .playful: - return "Bouncy, candy colors, pure delight" - case .artDeco: - return "1920s glamour with gold accents" - case .darkIndustrial: - return "Moody metallic dashboard aesthetic" - case .softPastel: - return "Light, airy, dreamy gradients" - case .flighty: - return "Aviation dashboard, data-rich elegance" - case .seatGeek: - return "Sports ticketing, vibrant modern cards" - case .appleMaps: - return "Native iOS, clean and familiar" - case .things3: - return "Ultra-clean, beautiful spacing" - case .airbnb: - return "Travel-focused, warm and inviting" - case .spotify: - return "Dark elegance, bold typography" - case .nikeRunClub: - return "Athletic stats, dynamic energy" - case .fantastical: - return "Calendar elegance, data-dense" - case .strava: - return "Athletic tracking, orange accent" - case .carrotWeather: - return "Bold personality, gradient skies" } } @@ -98,28 +27,6 @@ enum UIDesignStyle: String, CaseIterable, Identifiable, Codable { switch self { case .classic: return "star.fill" case .classicAnimated: return "sparkles" - case .brutalist: return "hammer.fill" - case .luxuryEditorial: return "book.fill" - case .retroFuturism: return "tv.fill" - case .glassmorphism: return "drop.fill" - case .neoBrutalist: return "square.fill" - case .organic: return "leaf.fill" - case .maximalistChaos: return "sparkles" - case .swissModernist: return "grid" - case .playful: return "face.smiling.fill" - case .artDeco: return "diamond.fill" - case .darkIndustrial: return "gearshape.fill" - case .softPastel: return "cloud.fill" - case .flighty: return "airplane" - case .seatGeek: return "ticket.fill" - case .appleMaps: return "map.fill" - case .things3: return "checkmark.circle.fill" - case .airbnb: return "house.fill" - case .spotify: return "waveform" - case .nikeRunClub: return "figure.run" - case .fantastical: return "calendar" - case .strava: return "location.fill" - case .carrotWeather: return "sun.max.fill" } } @@ -127,28 +34,6 @@ enum UIDesignStyle: String, CaseIterable, Identifiable, Codable { switch self { case .classic: return Color(red: 1.0, green: 0.45, blue: 0.2) // Warm Orange case .classicAnimated: return Color(red: 1.0, green: 0.45, blue: 0.2) // Warm Orange (same as Classic) - case .brutalist: return .red - case .luxuryEditorial: return Color(red: 0.85, green: 0.65, blue: 0.13) // Gold - case .retroFuturism: return Color(red: 0.0, green: 1.0, blue: 0.8) // Cyan - case .glassmorphism: return Color(red: 0.6, green: 0.4, blue: 1.0) // Purple - case .neoBrutalist: return Color(red: 1.0, green: 0.8, blue: 0.0) // Yellow - case .organic: return Color(red: 0.4, green: 0.7, blue: 0.4) // Green - case .maximalistChaos: return Color(red: 1.0, green: 0.2, blue: 0.6) // Magenta - case .swissModernist: return .red - case .playful: return Color(red: 1.0, green: 0.4, blue: 0.6) // Pink - case .artDeco: return Color(red: 0.85, green: 0.65, blue: 0.13) // Gold - case .darkIndustrial: return Color(red: 1.0, green: 0.5, blue: 0.0) // Orange - case .softPastel: return Color(red: 0.7, green: 0.8, blue: 1.0) // Soft blue - case .flighty: return Color(red: 0.2, green: 0.5, blue: 1.0) // Blue - case .seatGeek: return Color(red: 0.85, green: 0.15, blue: 0.5) // Magenta - case .appleMaps: return Color(red: 0.0, green: 0.48, blue: 1.0) // Apple Blue - case .things3: return Color(red: 0.35, green: 0.6, blue: 0.95) // Things Blue - case .airbnb: return Color(red: 1.0, green: 0.22, blue: 0.4) // Airbnb Red - case .spotify: return Color(red: 0.12, green: 0.84, blue: 0.38) // Spotify Green - case .nikeRunClub: return Color(red: 0.77, green: 1.0, blue: 0.0) // Nike Volt - case .fantastical: return Color(red: 0.92, green: 0.26, blue: 0.26) // Fantastical Red - case .strava: return Color(red: 0.99, green: 0.32, blue: 0.15) // Strava Orange - case .carrotWeather: return Color(red: 1.0, green: 0.45, blue: 0.2) // Carrot Orange } } } @@ -179,6 +64,12 @@ final class DesignStyleManager { func setStyle(_ style: UIDesignStyle) { currentStyle = style } + + /// Convenience property for animation toggle + var animationsEnabled: Bool { + get { currentStyle == .classicAnimated } + set { currentStyle = newValue ? .classicAnimated : .classic } + } } // MARK: - Environment Key diff --git a/SportsTime/Features/Home/Views/AdaptiveHomeContent.swift b/SportsTime/Features/Home/Views/AdaptiveHomeContent.swift index 16caebe..612597a 100644 --- a/SportsTime/Features/Home/Views/AdaptiveHomeContent.swift +++ b/SportsTime/Features/Home/Views/AdaptiveHomeContent.swift @@ -38,226 +38,6 @@ struct AdaptiveHomeContent: View { suggestedTripsGenerator: suggestedTripsGenerator, displayedTips: displayedTips ) - - case .brutalist: - HomeContent_Brutalist( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .luxuryEditorial: - HomeContent_LuxuryEditorial( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .retroFuturism: - HomeContent_RetroFuturism( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .glassmorphism: - HomeContent_Glassmorphism( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .neoBrutalist: - HomeContent_NeoBrutalist( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .organic: - HomeContent_Organic( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .maximalistChaos: - HomeContent_MaximalistChaos( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .swissModernist: - HomeContent_SwissModernist( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .playful: - HomeContent_Playful( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .artDeco: - HomeContent_ArtDeco( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .darkIndustrial: - HomeContent_DarkIndustrial( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .softPastel: - HomeContent_SoftPastel( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .flighty: - HomeContent_Flighty( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .seatGeek: - HomeContent_SeatGeek( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .appleMaps: - HomeContent_AppleMaps( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .things3: - HomeContent_Things3( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .airbnb: - HomeContent_Airbnb( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .spotify: - HomeContent_Spotify( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .nikeRunClub: - HomeContent_NikeRunClub( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .fantastical: - HomeContent_Fantastical( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .strava: - HomeContent_Strava( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) - - case .carrotWeather: - HomeContent_CarrotWeather( - showNewTrip: $showNewTrip, - selectedTab: $selectedTab, - selectedSuggestedTrip: $selectedSuggestedTrip, - savedTrips: savedTrips, - suggestedTripsGenerator: suggestedTripsGenerator, - displayedTips: displayedTips - ) } } } diff --git a/SportsTime/Features/Home/Views/Variants/Airbnb/HomeContent_Airbnb.swift b/SportsTime/Features/Home/Views/Variants/Airbnb/HomeContent_Airbnb.swift deleted file mode 100644 index 4eb520c..0000000 --- a/SportsTime/Features/Home/Views/Variants/Airbnb/HomeContent_Airbnb.swift +++ /dev/null @@ -1,332 +0,0 @@ -// -// HomeContent_Airbnb.swift -// SportsTime -// -// AIRBNB-INSPIRED: Travel-focused, warm aesthetic. -// Experience cards, inviting colors, rounded corners. -// Focus on discovery and exploration. -// - -import SwiftUI -import SwiftData - -struct HomeContent_Airbnb: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Airbnb-inspired colors - private var bgColor: Color { - colorScheme == .dark - ? Color(red: 0.08, green: 0.08, blue: 0.08) - : Color.white - } - - private let airbnbRed = Color(red: 1.0, green: 0.22, blue: 0.4) - private let warmGray = Color(red: 0.45, green: 0.42, blue: 0.4) - - private var textPrimary: Color { - colorScheme == .dark ? .white : Color(red: 0.14, green: 0.14, blue: 0.14) - } - - private var textSecondary: Color { - colorScheme == .dark ? Color(white: 0.55) : Color(red: 0.45, green: 0.45, blue: 0.45) - } - - var body: some View { - ScrollView { - VStack(spacing: 28) { - // Search bar - searchBar - .padding(.horizontal, 20) - .padding(.top, 8) - - // Categories - categoriesSection - - // Your trips - if !savedTrips.isEmpty { - yourTripsSection - } - - // Explore section - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - exploreSection - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 20) - } - - Spacer(minLength: 40) - } - } - .background(bgColor.ignoresSafeArea()) - } - - // MARK: - Search Bar - - private var searchBar: some View { - Button { - showNewTrip = true - } label: { - HStack(spacing: 14) { - Image(systemName: "magnifyingglass") - .font(.system(size: 18, weight: .medium)) - .foregroundStyle(textPrimary) - - VStack(alignment: .leading, spacing: 2) { - Text("Where to?") - .font(.system(size: 14, weight: .semibold)) - .foregroundStyle(textPrimary) - - Text("Anywhere • Any week • Any sport") - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - } - - Spacer() - - // Filter button - Circle() - .stroke(Color.gray.opacity(0.3), lineWidth: 1) - .frame(width: 36, height: 36) - .overlay( - Image(systemName: "slider.horizontal.3") - .font(.system(size: 14)) - .foregroundStyle(textPrimary) - ) - } - .padding(14) - .background( - RoundedRectangle(cornerRadius: 40) - .fill(colorScheme == .dark ? Color(white: 0.15) : Color.white) - .shadow(color: Color.black.opacity(0.12), radius: 12, y: 4) - ) - } - .buttonStyle(.plain) - } - - // MARK: - Categories Section - - private var categoriesSection: some View { - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 28) { - ForEach(Sport.supported) { sport in - categoryItem(sport) - } - } - .padding(.horizontal, 20) - } - } - - private func categoryItem(_ sport: Sport) -> some View { - Button { - showNewTrip = true - } label: { - VStack(spacing: 8) { - Image(systemName: sport.iconName) - .font(.system(size: 24)) - .foregroundStyle(textSecondary) - - Text(sport.displayName) - .font(.system(size: 11)) - .foregroundStyle(textSecondary) - } - .frame(width: 56) - } - .buttonStyle(.plain) - } - - // MARK: - Your Trips Section - - private var yourTripsSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Text("Your Trips") - .font(.system(size: 22, weight: .semibold)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("Show all") - .font(.system(size: 14, weight: .semibold)) - .foregroundStyle(textPrimary) - .underline() - } - } - .padding(.horizontal, 20) - - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 16) { - ForEach(savedTrips.prefix(4)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - tripCard(trip) - } - .buttonStyle(.plain) - } - } - } - .padding(.horizontal, 20) - } - } - } - - private func tripCard(_ trip: Trip) -> some View { - VStack(alignment: .leading, spacing: 10) { - // Image placeholder with gradient - ZStack { - RoundedRectangle(cornerRadius: 12) - .fill( - LinearGradient( - colors: [ - trip.uniqueSports.first?.themeColor ?? warmGray, - (trip.uniqueSports.first?.themeColor ?? warmGray).opacity(0.6) - ], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(height: 140) - - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt.fill") - .font(.system(size: 36)) - .foregroundStyle(.white.opacity(0.9)) - - // Favorite heart - VStack { - HStack { - Spacer() - Image(systemName: "heart") - .font(.system(size: 16)) - .foregroundStyle(.white) - .padding(10) - } - Spacer() - } - } - - VStack(alignment: .leading, spacing: 4) { - HStack { - Text(trip.displayName) - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Spacer() - - HStack(spacing: 2) { - Image(systemName: "star.fill") - .font(.system(size: 11)) - Text("New") - .font(.system(size: 12)) - } - .foregroundStyle(textPrimary) - } - - Text("\(trip.stops.count) cities • \(trip.totalGames) games") - .font(.system(size: 13)) - .foregroundStyle(textSecondary) - - Text(trip.formattedDateRange) - .font(.system(size: 13)) - .foregroundStyle(textSecondary) - } - } - .frame(width: 240) - } - - // MARK: - Explore Section - - private var exploreSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Text("Explore Routes") - .font(.system(size: 22, weight: .semibold)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 14)) - .foregroundStyle(textSecondary) - } - } - .padding(.horizontal, 20) - - VStack(spacing: 20) { - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(3)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - exploreCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - .padding(.horizontal, 20) - } - } - - private func exploreCard(_ trip: Trip) -> some View { - HStack(spacing: 14) { - // Small image - RoundedRectangle(cornerRadius: 10) - .fill( - LinearGradient( - colors: [ - trip.uniqueSports.first?.themeColor ?? warmGray, - (trip.uniqueSports.first?.themeColor ?? warmGray).opacity(0.7) - ], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 80, height: 80) - .overlay( - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt.fill") - .font(.system(size: 24)) - .foregroundStyle(.white.opacity(0.9)) - ) - - VStack(alignment: .leading, spacing: 6) { - Text(trip.displayName) - .font(.system(size: 15, weight: .medium)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Text("\(trip.stops.count) stops") - .font(.system(size: 13)) - .foregroundStyle(textSecondary) - - HStack(spacing: 4) { - Image(systemName: "sportscourt.fill") - .font(.system(size: 11)) - Text("\(trip.totalGames) games included") - .font(.system(size: 13)) - } - .foregroundStyle(textSecondary) - } - - Spacer() - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/AppleMaps/HomeContent_AppleMaps.swift b/SportsTime/Features/Home/Views/Variants/AppleMaps/HomeContent_AppleMaps.swift deleted file mode 100644 index 2149099..0000000 --- a/SportsTime/Features/Home/Views/Variants/AppleMaps/HomeContent_AppleMaps.swift +++ /dev/null @@ -1,322 +0,0 @@ -// -// HomeContent_AppleMaps.swift -// SportsTime -// -// APPLE MAPS-INSPIRED: Native iOS aesthetic. -// Clean cards, location-focused, subtle shadows. -// Professional and polished feel. -// - -import SwiftUI -import SwiftData - -struct HomeContent_AppleMaps: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Apple Maps-inspired colors - private var bgColor: Color { - colorScheme == .dark - ? Color(red: 0.0, green: 0.0, blue: 0.0) - : Color(red: 0.95, green: 0.95, blue: 0.97) - } - - private var cardBg: Color { - colorScheme == .dark - ? Color(red: 0.11, green: 0.11, blue: 0.12) - : Color.white - } - - private let mapsBlue = Color(red: 0.0, green: 0.48, blue: 1.0) - private let mapsGreen = Color(red: 0.2, green: 0.78, blue: 0.35) - - private var textPrimary: Color { - colorScheme == .dark ? .white : .black - } - - private var textSecondary: Color { - colorScheme == .dark ? Color(white: 0.6) : Color(white: 0.4) - } - - var body: some View { - ScrollView { - VStack(spacing: 16) { - // Search-style action card - searchCard - .padding(.horizontal, 16) - .padding(.top, 8) - - // Recents section - if !savedTrips.isEmpty { - recentsSection - } - - // Guides section (suggested trips) - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - guidesSection - } - - // Explore section - exploreSection - .padding(.horizontal, 16) - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 16) - } - - Spacer().frame(height: 32) - } - } - .background(bgColor.ignoresSafeArea()) - } - - // MARK: - Search Card - - private var searchCard: some View { - Button { - showNewTrip = true - } label: { - HStack(spacing: 12) { - Image(systemName: "magnifyingglass") - .font(.system(size: 17)) - .foregroundStyle(textSecondary) - - Text("Plan a trip or search destinations") - .font(.system(size: 17)) - .foregroundStyle(textSecondary) - - Spacer() - } - .padding(14) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.3 : 0.08), radius: 8, y: 2) - ) - } - .buttonStyle(.plain) - } - - // MARK: - Recents Section - - private var recentsSection: some View { - VStack(alignment: .leading, spacing: 12) { - HStack { - Text("Recents") - .font(.system(size: 22, weight: .bold)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("See All") - .font(.system(size: 15)) - .foregroundStyle(mapsBlue) - } - } - .padding(.horizontal, 16) - - VStack(spacing: 0) { - ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - recentRow(trip, isLast: index == min(2, savedTrips.count - 1)) - } - .buttonStyle(.plain) - } - } - } - .background( - RoundedRectangle(cornerRadius: 12) - .fill(cardBg) - ) - .padding(.horizontal, 16) - } - } - - private func recentRow(_ trip: Trip, isLast: Bool) -> some View { - VStack(spacing: 0) { - HStack(spacing: 14) { - // Location pin icon - ZStack { - Circle() - .fill(mapsBlue.opacity(0.15)) - .frame(width: 36, height: 36) - - Image(systemName: "mappin.circle.fill") - .font(.system(size: 20)) - .foregroundStyle(mapsBlue) - } - - VStack(alignment: .leading, spacing: 3) { - Text(trip.displayName) - .font(.system(size: 16)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Text("\(trip.stops.count) stops • \(trip.totalGames) games") - .font(.system(size: 13)) - .foregroundStyle(textSecondary) - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 13, weight: .medium)) - .foregroundStyle(Color(white: 0.75)) - } - .padding(.horizontal, 16) - .padding(.vertical, 12) - - if !isLast { - Divider() - .padding(.leading, 66) - } - } - } - - // MARK: - Guides Section - - private var guidesSection: some View { - VStack(alignment: .leading, spacing: 12) { - HStack { - Text("Suggested Routes") - .font(.system(size: 22, weight: .bold)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 15)) - .foregroundStyle(mapsBlue) - } - } - .padding(.horizontal, 16) - - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 12) { - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(4)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - guideCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - .padding(.horizontal, 16) - } - } - } - - private func guideCard(_ trip: Trip) -> some View { - VStack(alignment: .leading, spacing: 10) { - // Header with icon - ZStack { - RoundedRectangle(cornerRadius: 10) - .fill( - LinearGradient( - colors: [mapsGreen, mapsGreen.opacity(0.8)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(height: 80) - - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt.fill") - .font(.system(size: 28)) - .foregroundStyle(.white) - } - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(textPrimary) - .lineLimit(2) - .multilineTextAlignment(.leading) - - Text("\(trip.stops.count) places") - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - } - .padding(.horizontal, 10) - .padding(.bottom, 10) - } - .frame(width: 150) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.25 : 0.06), radius: 6, y: 2) - ) - } - - // MARK: - Explore Section - - private var exploreSection: some View { - VStack(alignment: .leading, spacing: 12) { - Text("Explore") - .font(.system(size: 22, weight: .bold)) - .foregroundStyle(textPrimary) - - VStack(spacing: 0) { - ForEach(Array(Sport.supported.prefix(4).enumerated()), id: \.element.id) { index, sport in - Button { - showNewTrip = true - } label: { - exploreRow(sport, isLast: index == min(3, Sport.supported.count - 1)) - } - .buttonStyle(.plain) - } - } - .background( - RoundedRectangle(cornerRadius: 12) - .fill(cardBg) - ) - } - } - - private func exploreRow(_ sport: Sport, isLast: Bool) -> some View { - VStack(spacing: 0) { - HStack(spacing: 14) { - Image(systemName: sport.iconName) - .font(.system(size: 18)) - .foregroundStyle(sport.themeColor) - .frame(width: 28) - - Text(sport.displayName) - .font(.system(size: 16)) - .foregroundStyle(textPrimary) - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 13, weight: .medium)) - .foregroundStyle(Color(white: 0.75)) - } - .padding(.horizontal, 16) - .padding(.vertical, 14) - - if !isLast { - Divider() - .padding(.leading, 58) - } - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/ArtDeco/HomeContent_ArtDeco.swift b/SportsTime/Features/Home/Views/Variants/ArtDeco/HomeContent_ArtDeco.swift deleted file mode 100644 index ec8a1c7..0000000 --- a/SportsTime/Features/Home/Views/Variants/ArtDeco/HomeContent_ArtDeco.swift +++ /dev/null @@ -1,492 +0,0 @@ -// -// HomeContent_ArtDeco.swift -// SportsTime -// -// ART DECO: 1920s glamour, geometric patterns, gold/black elegance. -// Sunburst motifs, stepped shapes, vintage stadium marquee vibes. -// - -import SwiftUI -import SwiftData - -struct HomeContent_ArtDeco: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Art Deco palette - private let decoGold = Color(red: 0.85, green: 0.7, blue: 0.35) - private let decoTeal = Color(red: 0.0, green: 0.5, blue: 0.5) - private let decoCream = Color(red: 0.98, green: 0.95, blue: 0.88) - - private var bgColor: Color { - colorScheme == .dark ? Color(red: 0.08, green: 0.06, blue: 0.1) : decoCream - } - - private var textPrimary: Color { - colorScheme == .dark ? decoCream : Color(red: 0.1, green: 0.08, blue: 0.06) - } - - private var textSecondary: Color { - colorScheme == .dark ? decoCream.opacity(0.6) : Color(red: 0.1, green: 0.08, blue: 0.06).opacity(0.6) - } - - var body: some View { - ZStack { - bgColor.ignoresSafeArea() - - // Deco pattern overlay - decoPatternOverlay - - ScrollView { - VStack(spacing: 0) { - // DECO MARQUEE HEADER - decoMarquee - .padding(.top, 24) - - // DECO HERO - decoHero - .padding(.top, 32) - .padding(.horizontal, 24) - - // FEATURED TRIPS - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - featuredSection - .padding(.top, 48) - .padding(.horizontal, 24) - } - - // SAVED TRIPS - if !savedTrips.isEmpty { - savedSection - .padding(.top, 48) - .padding(.horizontal, 24) - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.top, 48) - .padding(.horizontal, 24) - } - - // DECO FOOTER - decoFooter - .padding(.top, 56) - .padding(.bottom, 32) - } - } - } - } - - // MARK: - Deco Pattern Overlay - - private var decoPatternOverlay: some View { - GeometryReader { geo in - // Corner fan patterns - ZStack { - // Top corners - decoFan - .frame(width: 120, height: 120) - .position(x: 0, y: 0) - - decoFan - .frame(width: 120, height: 120) - .scaleEffect(x: -1) - .position(x: geo.size.width, y: 0) - - // Vertical lines accent - HStack(spacing: 40) { - ForEach(0..<5, id: \.self) { _ in - Rectangle() - .fill(decoGold.opacity(0.08)) - .frame(width: 1) - } - } - .frame(height: geo.size.height) - } - } - .allowsHitTesting(false) - } - - private var decoFan: some View { - ZStack { - ForEach(0..<5, id: \.self) { i in - Rectangle() - .fill(decoGold.opacity(0.1)) - .frame(width: 2, height: 80) - .rotationEffect(.degrees(Double(i) * 15 - 30)) - .offset(y: -40) - } - } - } - - // MARK: - Deco Marquee - - private var decoMarquee: some View { - VStack(spacing: 0) { - // Top decorative border - HStack(spacing: 8) { - decoCorner - Rectangle() - .fill(decoGold) - .frame(height: 2) - decoCorner - .scaleEffect(x: -1) - } - .frame(height: 20) - .padding(.horizontal, 16) - - // Marquee content - VStack(spacing: 4) { - Text("★ SPORTS TIME ★") - .font(.system(size: 12, weight: .bold)) - .tracking(6) - .foregroundStyle(decoGold) - - Text("EST. MMXXVI") - .font(.system(size: 9, weight: .medium)) - .tracking(4) - .foregroundStyle(textSecondary) - } - .padding(.vertical, 12) - - // Bottom decorative border - HStack(spacing: 8) { - decoCorner - .scaleEffect(y: -1) - Rectangle() - .fill(decoGold) - .frame(height: 2) - decoCorner - .scaleEffect(x: -1, y: -1) - } - .frame(height: 20) - .padding(.horizontal, 16) - } - } - - private var decoCorner: some View { - ZStack { - Rectangle() - .fill(decoGold) - .frame(width: 20, height: 2) - - Rectangle() - .fill(decoGold) - .frame(width: 2, height: 20) - .offset(x: -9) - } - } - - // MARK: - Deco Hero - - private var decoHero: some View { - VStack(spacing: 24) { - // Sunburst title - ZStack { - // Rays - ForEach(0..<12, id: \.self) { i in - Rectangle() - .fill(decoGold.opacity(0.15)) - .frame(width: 2, height: 60) - .offset(y: -50) - .rotationEffect(.degrees(Double(i) * 30)) - } - - // Title container - VStack(spacing: 4) { - Text("YOUR") - .font(.system(size: 14, weight: .medium)) - .tracking(8) - .foregroundStyle(textSecondary) - - Text("JOURNEY") - .font(.system(size: 36, weight: .bold)) - .tracking(4) - .foregroundStyle(textPrimary) - - Text("AWAITS") - .font(.system(size: 14, weight: .medium)) - .tracking(8) - .foregroundStyle(textSecondary) - } - } - .padding(.vertical, 20) - - // Description with deco borders - VStack(spacing: 12) { - decoLine - Text("Plan an unforgettable road trip through America's greatest stadiums and arenas.") - .font(.system(size: 15, weight: .regular)) - .foregroundStyle(textSecondary) - .multilineTextAlignment(.center) - .lineSpacing(6) - .padding(.horizontal, 16) - decoLine - } - - // Deco CTA Button - Button { - showNewTrip = true - } label: { - HStack(spacing: 16) { - decoDiamond - Text("BEGIN") - .font(.system(size: 14, weight: .bold)) - .tracking(4) - decoDiamond - } - .foregroundStyle(colorScheme == .dark ? .black : decoCream) - .padding(.horizontal, 40) - .padding(.vertical, 18) - .background( - Rectangle() - .fill(decoGold) - ) - .overlay( - Rectangle() - .stroke(decoGold.opacity(0.5), lineWidth: 1) - .padding(4) - ) - } - } - .padding(32) - .background( - RoundedRectangle(cornerRadius: 0) - .fill(colorScheme == .dark ? Color.white.opacity(0.03) : Color.white.opacity(0.5)) - .overlay( - Rectangle() - .stroke(decoGold.opacity(0.3), lineWidth: 1) - ) - ) - } - - private var decoLine: some View { - HStack(spacing: 12) { - Rectangle() - .fill(decoGold.opacity(0.4)) - .frame(height: 1) - decoDiamond - .foregroundStyle(decoGold.opacity(0.6)) - Rectangle() - .fill(decoGold.opacity(0.4)) - .frame(height: 1) - } - } - - private var decoDiamond: some View { - Rectangle() - .fill(decoGold) - .frame(width: 6, height: 6) - .rotationEffect(.degrees(45)) - } - - // MARK: - Featured Section - - private var featuredSection: some View { - VStack(alignment: .leading, spacing: 20) { - // Section header - HStack { - VStack(alignment: .leading, spacing: 4) { - Text("FEATURED") - .font(.system(size: 11, weight: .bold)) - .tracking(4) - .foregroundStyle(decoGold) - - Text("Itineraries") - .font(.system(size: 24, weight: .bold)) - .foregroundStyle(textPrimary) - } - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 14)) - .foregroundStyle(decoGold) - .padding(12) - .overlay( - Rectangle() - .stroke(decoGold.opacity(0.5), lineWidth: 1) - ) - } - } - - // Deco cards - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(4)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - decoTripCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - } - - private func decoTripCard(_ trip: Trip) -> some View { - HStack(spacing: 16) { - // Stepped shape icon container - ZStack { - // Stepped background - Rectangle() - .fill(decoGold.opacity(0.15)) - .frame(width: 50, height: 50) - - Rectangle() - .fill(decoGold.opacity(0.1)) - .frame(width: 44, height: 44) - .offset(x: 3, y: 3) - - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt") - .font(.system(size: 20)) - .foregroundStyle(decoGold) - } - - VStack(alignment: .leading, spacing: 6) { - Text(trip.displayName.uppercased()) - .font(.system(size: 14, weight: .bold)) - .tracking(1) - .foregroundStyle(textPrimary) - .lineLimit(1) - - HStack(spacing: 16) { - HStack(spacing: 4) { - decoDiamond - .scaleEffect(0.6) - .foregroundStyle(decoTeal) - Text("\(trip.stops.count) CITIES") - .font(.system(size: 10, weight: .medium)) - .tracking(1) - .foregroundStyle(textSecondary) - } - - HStack(spacing: 4) { - decoDiamond - .scaleEffect(0.6) - .foregroundStyle(decoTeal) - Text("\(trip.totalGames) GAMES") - .font(.system(size: 10, weight: .medium)) - .tracking(1) - .foregroundStyle(textSecondary) - } - } - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 12, weight: .medium)) - .foregroundStyle(decoGold) - } - .padding(16) - .background( - Rectangle() - .fill(colorScheme == .dark ? Color.white.opacity(0.03) : Color.white.opacity(0.6)) - ) - .overlay( - Rectangle() - .stroke(decoGold.opacity(0.2), lineWidth: 1) - ) - } - - // MARK: - Saved Section - - private var savedSection: some View { - VStack(alignment: .leading, spacing: 20) { - HStack { - VStack(alignment: .leading, spacing: 4) { - Text("YOUR") - .font(.system(size: 11, weight: .bold)) - .tracking(4) - .foregroundStyle(decoTeal) - - Text("Collection") - .font(.system(size: 24, weight: .bold)) - .foregroundStyle(textPrimary) - } - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("VIEW ALL") - .font(.system(size: 10, weight: .bold)) - .tracking(2) - .foregroundStyle(decoGold) - } - } - - ForEach(savedTrips.prefix(3)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - HStack { - decoDiamond - .foregroundStyle(decoTeal) - - Text(trip.displayName) - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(textPrimary) - - Spacer() - - Text("\(trip.stops.count)") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(decoGold) - .padding(.horizontal, 12) - .padding(.vertical, 6) - .background( - Rectangle() - .fill(decoGold.opacity(0.15)) - ) - } - .padding(.vertical, 14) - .overlay(alignment: .bottom) { - Rectangle() - .fill(decoGold.opacity(0.15)) - .frame(height: 1) - } - } - .buttonStyle(.plain) - } - } - } - } - - // MARK: - Deco Footer - - private var decoFooter: some View { - VStack(spacing: 12) { - // Stepped pyramid - VStack(spacing: 2) { - Rectangle() - .fill(decoGold.opacity(0.4)) - .frame(width: 30, height: 2) - Rectangle() - .fill(decoGold.opacity(0.3)) - .frame(width: 50, height: 2) - Rectangle() - .fill(decoGold.opacity(0.2)) - .frame(width: 70, height: 2) - } - - Text("SPORTS TIME") - .font(.system(size: 9, weight: .bold)) - .tracking(6) - .foregroundStyle(textSecondary.opacity(0.5)) - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/Brutalist/HomeContent_Brutalist.swift b/SportsTime/Features/Home/Views/Variants/Brutalist/HomeContent_Brutalist.swift deleted file mode 100644 index 1ee8811..0000000 --- a/SportsTime/Features/Home/Views/Variants/Brutalist/HomeContent_Brutalist.swift +++ /dev/null @@ -1,280 +0,0 @@ -// -// HomeContent_Brutalist.swift -// SportsTime -// -// BRUTALIST: Raw, unpolished, anti-design rebellion. -// Monospace typography, harsh borders, no rounded corners. -// Ticket stub perforations, stadium concrete vibes. -// - -import SwiftUI -import SwiftData - -struct HomeContent_Brutalist: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - private var bgColor: Color { - colorScheme == .dark ? .black : Color(white: 0.95) - } - - private var textColor: Color { - colorScheme == .dark ? .white : .black - } - - var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 0) { - // HEADER - Raw, bold, commanding - brutalistHeader - - // PERFORATED DIVIDER - perforatedDivider - - // ACTION BLOCK - actionBlock - .padding(.vertical, 24) - - perforatedDivider - - // TRIPS SECTION - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - tripsSection - .padding(.vertical, 24) - - perforatedDivider - } - - // SAVED TRIPS - if !savedTrips.isEmpty { - savedSection - .padding(.vertical, 24) - - perforatedDivider - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.vertical, 24) - } - - // FOOTER STAMP - footerStamp - .padding(.vertical, 32) - } - .padding(.horizontal, 16) - } - .background(bgColor) - } - - // MARK: - Brutalist Header - - private var brutalistHeader: some View { - VStack(alignment: .leading, spacing: 8) { - // Date stamp - like a ticket - Text(Date.now.formatted(.dateTime.year().month().day()).uppercased()) - .font(.system(.caption, design: .monospaced)) - .foregroundStyle(textColor.opacity(0.5)) - .padding(.top, 24) - - // Main title - LOUD - Text("SPORTS") - .font(.system(size: 64, weight: .black, design: .default)) - .foregroundStyle(textColor) - .tracking(-2) - - Text("TIME") - .font(.system(size: 64, weight: .black, design: .default)) - .foregroundStyle(.red) - .tracking(-2) - .offset(y: -16) - - // Subtitle - Text("PLAN YOUR ROAD TRIP") - .font(.system(.subheadline, design: .monospaced)) - .foregroundStyle(textColor.opacity(0.7)) - .tracking(4) - } - } - - // MARK: - Perforated Divider (Ticket Stub Style) - - private var perforatedDivider: some View { - HStack(spacing: 8) { - ForEach(0..<20, id: \.self) { _ in - Circle() - .fill(textColor.opacity(0.3)) - .frame(width: 6, height: 6) - } - } - .frame(maxWidth: .infinity) - } - - // MARK: - Action Block - - private var actionBlock: some View { - VStack(alignment: .leading, spacing: 16) { - Text("[ NEW TRIP ]") - .font(.system(.caption, design: .monospaced)) - .foregroundStyle(textColor.opacity(0.5)) - - Button { - showNewTrip = true - } label: { - HStack { - Text("START PLANNING →") - .font(.system(.title3, design: .monospaced).bold()) - - Spacer() - } - .foregroundStyle(colorScheme == .dark ? .black : .white) - .padding(20) - .background(.red) - } - - Text("CREATE YOUR ULTIMATE SPORTS ROAD TRIP. NO FRILLS. JUST GAMES.") - .font(.system(.caption, design: .monospaced)) - .foregroundStyle(textColor.opacity(0.6)) - .lineSpacing(4) - } - } - - // MARK: - Trips Section - - private var tripsSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Text("[ FEATURED ]") - .font(.system(.caption, design: .monospaced)) - .foregroundStyle(textColor.opacity(0.5)) - - Spacer() - - Text("\(suggestedTripsGenerator.suggestedTrips.count) TRIPS") - .font(.system(.caption, design: .monospaced)) - .foregroundStyle(.red) - } - - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(4)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - brutalistTripCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - } - - private func brutalistTripCard(_ trip: Trip) -> some View { - VStack(alignment: .leading, spacing: 8) { - HStack(alignment: .top) { - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName.uppercased()) - .font(.system(.headline, design: .monospaced).bold()) - .foregroundStyle(textColor) - - Text("\(trip.stops.count) STOPS • \(trip.totalGames) GAMES") - .font(.system(.caption2, design: .monospaced)) - .foregroundStyle(textColor.opacity(0.5)) - } - - Spacer() - - // Sport icons as harsh symbols - HStack(spacing: 4) { - ForEach(Array(trip.uniqueSports.prefix(3)), id: \.self) { sport in - Text("●") - .font(.caption) - .foregroundStyle(sport.themeColor) - } - } - } - - // Route as raw text - Text(trip.stops.map { $0.city.uppercased() }.joined(separator: " → ")) - .font(.system(.caption2, design: .monospaced)) - .foregroundStyle(textColor.opacity(0.4)) - .lineLimit(1) - } - .padding(16) - .background(textColor.opacity(0.05)) - .border(textColor.opacity(0.2), width: 1) - } - - // MARK: - Saved Section - - private var savedSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Text("[ YOUR TRIPS ]") - .font(.system(.caption, design: .monospaced)) - .foregroundStyle(textColor.opacity(0.5)) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("VIEW ALL →") - .font(.system(.caption, design: .monospaced)) - .foregroundStyle(.red) - } - } - - ForEach(savedTrips.prefix(3)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - HStack { - Text(trip.displayName.uppercased()) - .font(.system(.subheadline, design: .monospaced)) - .foregroundStyle(textColor) - - Spacer() - - Text("\(trip.stops.count)") - .font(.system(.caption, design: .monospaced)) - .foregroundStyle(textColor.opacity(0.5)) - } - .padding(.vertical, 12) - .overlay(alignment: .bottom) { - Rectangle() - .fill(textColor.opacity(0.1)) - .frame(height: 1) - } - } - .buttonStyle(.plain) - } - } - } - } - - // MARK: - Footer Stamp - - private var footerStamp: some View { - VStack(spacing: 8) { - Rectangle() - .fill(.red) - .frame(width: 60, height: 4) - - Text("SPORTSTIME") - .font(.system(.caption2, design: .monospaced)) - .foregroundStyle(textColor.opacity(0.3)) - .tracking(6) - - Text("EST. 2024") - .font(.system(.caption2, design: .monospaced)) - .foregroundStyle(textColor.opacity(0.2)) - } - .frame(maxWidth: .infinity) - } -} diff --git a/SportsTime/Features/Home/Views/Variants/CarrotWeather/HomeContent_CarrotWeather.swift b/SportsTime/Features/Home/Views/Variants/CarrotWeather/HomeContent_CarrotWeather.swift deleted file mode 100644 index 5fd02e9..0000000 --- a/SportsTime/Features/Home/Views/Variants/CarrotWeather/HomeContent_CarrotWeather.swift +++ /dev/null @@ -1,353 +0,0 @@ -// -// HomeContent_CarrotWeather.swift -// SportsTime -// -// CARROT WEATHER-INSPIRED: Bold personality, clean layout. -// Dynamic content, good use of gradients. -// Data-focused with character. -// - -import SwiftUI -import SwiftData - -struct HomeContent_CarrotWeather: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Carrot-inspired colors - private var bgGradient: LinearGradient { - LinearGradient( - colors: colorScheme == .dark - ? [Color(red: 0.1, green: 0.12, blue: 0.18), Color(red: 0.08, green: 0.08, blue: 0.12)] - : [Color(red: 0.45, green: 0.65, blue: 0.9), Color(red: 0.35, green: 0.55, blue: 0.85)], - startPoint: .top, - endPoint: .bottom - ) - } - - private var cardBg: Color { - colorScheme == .dark - ? Color.white.opacity(0.08) - : Color.white.opacity(0.25) - } - - private let carrotOrange = Color(red: 1.0, green: 0.45, blue: 0.2) - - private var textPrimary: Color { - .white - } - - private var textSecondary: Color { - Color.white.opacity(0.7) - } - - var body: some View { - ScrollView { - VStack(spacing: 24) { - // Main display - mainDisplay - .padding(.horizontal, 20) - .padding(.top, 20) - - // Trip summary card - tripSummaryCard - .padding(.horizontal, 20) - - // Recent trips - if !savedTrips.isEmpty { - recentTripsSection - } - - // Suggestions - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - suggestionsSection - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 20) - } - - Spacer(minLength: 50) - } - } - .background(bgGradient.ignoresSafeArea()) - } - - // MARK: - Main Display - - private var mainDisplay: some View { - VStack(spacing: 12) { - // Big number display (like temperature) - Text("\(savedTrips.count)") - .font(.system(size: 96, weight: .thin)) - .foregroundStyle(textPrimary) - - Text(tripStatusMessage) - .font(.system(size: 18, weight: .medium)) - .foregroundStyle(textSecondary) - .multilineTextAlignment(.center) - - // Personality message - Text(personalityMessage) - .font(.system(size: 14)) - .foregroundStyle(textSecondary.opacity(0.8)) - .italic() - .padding(.top, 4) - } - } - - private var tripStatusMessage: String { - if savedTrips.isEmpty { - return "No trips planned" - } else if savedTrips.count == 1 { - return "Trip planned" - } else { - return "Trips in your queue" - } - } - - private var personalityMessage: String { - if savedTrips.isEmpty { - return "Time to hit the road, sports fan!" - } else if savedTrips.count >= 3 { - return "Someone's serious about their sports!" - } else { - return "Adventure awaits..." - } - } - - // MARK: - Trip Summary Card - - private var tripSummaryCard: some View { - Button { - showNewTrip = true - } label: { - VStack(spacing: 16) { - HStack(spacing: 20) { - summaryItem(value: "\(totalGames)", label: "Games", icon: "sportscourt.fill") - summaryItem(value: "\(totalStops)", label: "Cities", icon: "building.2.fill") - summaryItem(value: "\(uniqueSportsCount)", label: "Sports", icon: "figure.run") - } - - // CTA - HStack { - Text("Plan a Trip") - .font(.system(size: 15, weight: .semibold)) - - Image(systemName: "arrow.right") - .font(.system(size: 13, weight: .semibold)) - } - .foregroundStyle(textPrimary) - .padding(.horizontal, 20) - .padding(.vertical, 10) - .background( - Capsule() - .fill(carrotOrange) - ) - } - .padding(20) - .background( - RoundedRectangle(cornerRadius: 20) - .fill(cardBg) - .background( - RoundedRectangle(cornerRadius: 20) - .fill(.ultraThinMaterial) - ) - ) - } - .buttonStyle(.plain) - } - - private func summaryItem(value: String, label: String, icon: String) -> some View { - VStack(spacing: 6) { - Image(systemName: icon) - .font(.system(size: 18)) - .foregroundStyle(textSecondary) - - Text(value) - .font(.system(size: 22, weight: .semibold)) - .foregroundStyle(textPrimary) - - Text(label) - .font(.system(size: 11)) - .foregroundStyle(textSecondary) - } - .frame(maxWidth: .infinity) - } - - private var totalGames: Int { - savedTrips.compactMap { $0.trip?.totalGames }.reduce(0, +) - } - - private var totalStops: Int { - savedTrips.compactMap { $0.trip?.stops.count }.reduce(0, +) - } - - private var uniqueSportsCount: Int { - Set(savedTrips.flatMap { $0.trip?.uniqueSports ?? [] }).count - } - - // MARK: - Recent Trips Section - - private var recentTripsSection: some View { - VStack(alignment: .leading, spacing: 14) { - HStack { - Text("YOUR TRIPS") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(textSecondary) - .tracking(1) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("See All") - .font(.system(size: 13, weight: .medium)) - .foregroundStyle(textPrimary) - } - } - .padding(.horizontal, 20) - - VStack(spacing: 10) { - ForEach(savedTrips.prefix(3)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - tripRow(trip) - } - .buttonStyle(.plain) - } - } - } - .padding(.horizontal, 20) - } - } - - private func tripRow(_ trip: Trip) -> some View { - HStack(spacing: 14) { - // Sport indicator - Circle() - .fill(trip.uniqueSports.first?.themeColor ?? carrotOrange) - .frame(width: 40, height: 40) - .overlay( - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt.fill") - .font(.system(size: 16)) - .foregroundStyle(.white) - ) - - VStack(alignment: .leading, spacing: 3) { - Text(trip.displayName) - .font(.system(size: 15, weight: .medium)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Text("\(trip.stops.count) stops • \(trip.totalGames) games") - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 12)) - .foregroundStyle(textSecondary.opacity(0.6)) - } - .padding(14) - .background( - RoundedRectangle(cornerRadius: 14) - .fill(cardBg) - .background( - RoundedRectangle(cornerRadius: 14) - .fill(.ultraThinMaterial) - ) - ) - } - - // MARK: - Suggestions Section - - private var suggestionsSection: some View { - VStack(alignment: .leading, spacing: 14) { - HStack { - Text("SUGGESTED") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(textSecondary) - .tracking(1) - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 13)) - .foregroundStyle(textPrimary) - } - } - .padding(.horizontal, 20) - - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 12) { - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(4)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - suggestionCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - .padding(.horizontal, 20) - } - } - } - - private func suggestionCard(_ trip: Trip) -> some View { - VStack(alignment: .leading, spacing: 10) { - // Header with big stat - VStack(spacing: 4) { - Text("\(trip.totalGames)") - .font(.system(size: 32, weight: .semibold)) - .foregroundStyle(textPrimary) - - Text("games") - .font(.system(size: 11)) - .foregroundStyle(textSecondary) - } - .frame(maxWidth: .infinity) - .padding(.vertical, 12) - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 13, weight: .medium)) - .foregroundStyle(textPrimary) - .lineLimit(2) - - Text("\(trip.stops.count) cities") - .font(.system(size: 11)) - .foregroundStyle(textSecondary) - } - } - .frame(width: 130) - .padding(14) - .background( - RoundedRectangle(cornerRadius: 16) - .fill(cardBg) - .background( - RoundedRectangle(cornerRadius: 16) - .fill(.ultraThinMaterial) - ) - ) - } -} diff --git a/SportsTime/Features/Home/Views/Variants/DarkIndustrial/HomeContent_DarkIndustrial.swift b/SportsTime/Features/Home/Views/Variants/DarkIndustrial/HomeContent_DarkIndustrial.swift deleted file mode 100644 index 319475e..0000000 --- a/SportsTime/Features/Home/Views/Variants/DarkIndustrial/HomeContent_DarkIndustrial.swift +++ /dev/null @@ -1,434 +0,0 @@ -// -// HomeContent_DarkIndustrial.swift -// SportsTime -// -// DARK INDUSTRIAL: Steel, concrete, utility. -// Stadium infrastructure vibes, warning stripes, exposed structure. -// Functional brutalism with sports facility aesthetics. -// - -import SwiftUI -import SwiftData - -struct HomeContent_DarkIndustrial: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Industrial palette - private let warningYellow = Color(red: 1.0, green: 0.8, blue: 0.0) - private let steelGray = Color(red: 0.4, green: 0.45, blue: 0.5) - private let concreteGray = Color(red: 0.3, green: 0.32, blue: 0.35) - private let alertRed = Color(red: 0.9, green: 0.2, blue: 0.2) - - private var bgColor: Color { - colorScheme == .dark ? Color(red: 0.08, green: 0.09, blue: 0.1) : Color(red: 0.15, green: 0.16, blue: 0.18) - } - - private var textPrimary: Color { - Color(white: 0.9) - } - - private var textSecondary: Color { - Color(white: 0.55) - } - - var body: some View { - ZStack { - bgColor.ignoresSafeArea() - - // Industrial texture overlay - industrialTexture - - ScrollView { - VStack(spacing: 0) { - // WARNING STRIPE HEADER - warningStripeHeader - - // INDUSTRIAL HERO - industrialHero - .padding(.top, 32) - .padding(.horizontal, 20) - - // FEATURED TRIPS - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - featuredSection - .padding(.top, 40) - .padding(.horizontal, 20) - } - - // SAVED TRIPS - if !savedTrips.isEmpty { - savedSection - .padding(.top, 40) - .padding(.horizontal, 20) - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.top, 40) - .padding(.horizontal, 20) - } - - // INDUSTRIAL FOOTER - industrialFooter - .padding(.top, 48) - .padding(.bottom, 32) - } - } - } - } - - // MARK: - Industrial Texture - - private var industrialTexture: some View { - ZStack { - // Grid pattern (exposed structure) - GeometryReader { geo in - Path { path in - let spacing: CGFloat = 40 - for x in stride(from: CGFloat(0), to: geo.size.width, by: spacing) { - path.move(to: CGPoint(x: x, y: 0)) - path.addLine(to: CGPoint(x: x, y: geo.size.height)) - } - for y in stride(from: CGFloat(0), to: geo.size.height, by: spacing) { - path.move(to: CGPoint(x: 0, y: y)) - path.addLine(to: CGPoint(x: geo.size.width, y: y)) - } - } - .stroke(steelGray.opacity(0.1), lineWidth: 0.5) - } - - // Corner rivets/bolts - VStack { - HStack { - rivetCluster - Spacer() - rivetCluster - } - Spacer() - } - .padding(20) - } - .allowsHitTesting(false) - } - - private var rivetCluster: some View { - HStack(spacing: 8) { - rivet - rivet - } - } - - private var rivet: some View { - Circle() - .fill(steelGray.opacity(0.3)) - .frame(width: 8, height: 8) - .overlay( - Circle() - .fill(steelGray.opacity(0.5)) - .frame(width: 4, height: 4) - ) - } - - // MARK: - Warning Stripe Header - - private var warningStripeHeader: some View { - VStack(spacing: 0) { - // Warning stripes - HStack(spacing: 0) { - ForEach(0..<20, id: \.self) { i in - Rectangle() - .fill(i % 2 == 0 ? warningYellow : .black) - .frame(width: 20, height: 8) - } - } - - // System status bar - HStack { - HStack(spacing: 6) { - Circle() - .fill(Color.green) - .frame(width: 6, height: 6) - Text("SYSTEM ONLINE") - .font(.system(size: 9, weight: .bold, design: .monospaced)) - .foregroundStyle(textSecondary) - } - - Spacer() - - Text(Date.now.formatted(.dateTime.month().day().year())) - .font(.system(size: 9, weight: .medium, design: .monospaced)) - .foregroundStyle(textSecondary) - } - .padding(.horizontal, 16) - .padding(.vertical, 10) - .background(concreteGray.opacity(0.5)) - } - } - - // MARK: - Industrial Hero - - private var industrialHero: some View { - VStack(alignment: .leading, spacing: 24) { - // Sector label - HStack(spacing: 8) { - Rectangle() - .fill(warningYellow) - .frame(width: 4, height: 20) - - Text("SECTOR A-1") - .font(.system(size: 10, weight: .bold, design: .monospaced)) - .foregroundStyle(warningYellow) - } - - // Main title - stencil style - VStack(alignment: .leading, spacing: 4) { - Text("SPORTS") - .font(.system(size: 42, weight: .black)) - .foregroundStyle(textPrimary) - - Text("TIME") - .font(.system(size: 42, weight: .black)) - .foregroundStyle(warningYellow) - } - - // Description panel - VStack(alignment: .leading, spacing: 8) { - Text("// TRIP PLANNING SYSTEM") - .font(.system(size: 10, weight: .medium, design: .monospaced)) - .foregroundStyle(steelGray) - - Text("Route optimization for stadium road trips. Multi-sport scheduling. Real-time coordination.") - .font(.system(size: 14, weight: .regular)) - .foregroundStyle(textSecondary) - .lineSpacing(4) - } - .padding(16) - .background( - Rectangle() - .fill(concreteGray.opacity(0.3)) - .overlay( - Rectangle() - .stroke(steelGray.opacity(0.3), lineWidth: 1) - ) - ) - - // Industrial CTA - Button { - showNewTrip = true - } label: { - HStack { - Image(systemName: "play.fill") - .font(.system(size: 12)) - - Text("INITIATE PLANNING") - .font(.system(size: 14, weight: .bold, design: .monospaced)) - - Spacer() - - Text("▶") - .font(.system(size: 16, weight: .bold)) - } - .foregroundStyle(.black) - .padding(18) - .background(warningYellow) - } - } - .padding(24) - .background( - Rectangle() - .fill(bgColor) - .overlay( - Rectangle() - .stroke(steelGray.opacity(0.3), lineWidth: 2) - ) - ) - } - - // MARK: - Featured Section - - private var featuredSection: some View { - VStack(alignment: .leading, spacing: 20) { - // Section header - HStack { - HStack(spacing: 8) { - Rectangle() - .fill(alertRed) - .frame(width: 4, height: 20) - - Text("FEATURED ROUTES") - .font(.system(size: 12, weight: .bold, design: .monospaced)) - .foregroundStyle(textPrimary) - } - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - HStack(spacing: 4) { - Image(systemName: "arrow.clockwise") - .font(.system(size: 12)) - Text("REFRESH") - .font(.system(size: 9, weight: .bold, design: .monospaced)) - } - .foregroundStyle(steelGray) - .padding(.horizontal, 12) - .padding(.vertical, 8) - .overlay( - Rectangle() - .stroke(steelGray.opacity(0.5), lineWidth: 1) - ) - } - } - - // Industrial cards - ForEach(Array(suggestedTripsGenerator.suggestedTrips.prefix(4).enumerated()), id: \.element.id) { index, suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - industrialTripCard(suggestedTrip.trip, index: index + 1) - } - .buttonStyle(.plain) - } - } - } - - private func industrialTripCard(_ trip: Trip, index: Int) -> some View { - HStack(spacing: 16) { - // Index plate - Text(String(format: "%02d", index)) - .font(.system(size: 18, weight: .bold, design: .monospaced)) - .foregroundStyle(.black) - .frame(width: 44, height: 44) - .background(warningYellow) - - VStack(alignment: .leading, spacing: 6) { - Text(trip.displayName.uppercased()) - .font(.system(size: 13, weight: .bold, design: .monospaced)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - HStack(spacing: 16) { - HStack(spacing: 4) { - Image(systemName: "mappin") - .font(.system(size: 10)) - Text("\(trip.stops.count) STOPS") - .font(.system(size: 10, weight: .medium, design: .monospaced)) - } - .foregroundStyle(steelGray) - - HStack(spacing: 4) { - Image(systemName: "sportscourt") - .font(.system(size: 10)) - Text("\(trip.totalGames) EVENTS") - .font(.system(size: 10, weight: .medium, design: .monospaced)) - } - .foregroundStyle(steelGray) - } - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(steelGray) - } - .padding(16) - .background( - Rectangle() - .fill(concreteGray.opacity(0.2)) - .overlay( - Rectangle() - .stroke(steelGray.opacity(0.2), lineWidth: 1) - ) - ) - } - - // MARK: - Saved Section - - private var savedSection: some View { - VStack(alignment: .leading, spacing: 20) { - HStack { - HStack(spacing: 8) { - Rectangle() - .fill(steelGray) - .frame(width: 4, height: 20) - - Text("SAVED ROUTES") - .font(.system(size: 12, weight: .bold, design: .monospaced)) - .foregroundStyle(textPrimary) - } - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("VIEW ALL ▶") - .font(.system(size: 9, weight: .bold, design: .monospaced)) - .foregroundStyle(steelGray) - } - } - - ForEach(savedTrips.prefix(3)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - HStack { - Rectangle() - .fill(steelGray.opacity(0.5)) - .frame(width: 3, height: 32) - - Text(trip.displayName.uppercased()) - .font(.system(size: 12, weight: .medium, design: .monospaced)) - .foregroundStyle(textPrimary) - - Spacer() - - Text("[\(trip.stops.count)]") - .font(.system(size: 11, weight: .bold, design: .monospaced)) - .foregroundStyle(warningYellow) - } - .padding(.vertical, 12) - .overlay(alignment: .bottom) { - Rectangle() - .fill(steelGray.opacity(0.2)) - .frame(height: 1) - } - } - .buttonStyle(.plain) - } - } - } - } - - // MARK: - Industrial Footer - - private var industrialFooter: some View { - VStack(spacing: 12) { - // Warning stripe - HStack(spacing: 0) { - ForEach(0..<8, id: \.self) { i in - Rectangle() - .fill(i % 2 == 0 ? warningYellow.opacity(0.3) : .clear) - .frame(width: 12, height: 4) - } - } - - Text("// SPORTS TIME SYSTEMS") - .font(.system(size: 9, weight: .medium, design: .monospaced)) - .foregroundStyle(steelGray.opacity(0.5)) - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/Fantastical/HomeContent_Fantastical.swift b/SportsTime/Features/Home/Views/Variants/Fantastical/HomeContent_Fantastical.swift deleted file mode 100644 index 19dd5f0..0000000 --- a/SportsTime/Features/Home/Views/Variants/Fantastical/HomeContent_Fantastical.swift +++ /dev/null @@ -1,344 +0,0 @@ -// -// HomeContent_Fantastical.swift -// SportsTime -// -// FANTASTICAL-INSPIRED: Calendar elegance. -// Data-dense but readable, rich colors. -// Schedule-focused with beautiful typography. -// - -import SwiftUI -import SwiftData - -struct HomeContent_Fantastical: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Fantastical-inspired colors - private var bgColor: Color { - colorScheme == .dark - ? Color(red: 0.1, green: 0.1, blue: 0.12) - : Color(red: 0.97, green: 0.97, blue: 0.98) - } - - private var cardBg: Color { - colorScheme == .dark - ? Color(red: 0.15, green: 0.15, blue: 0.17) - : Color.white - } - - private let fantasticalRed = Color(red: 0.92, green: 0.26, blue: 0.26) - private let fantasticalBlue = Color(red: 0.2, green: 0.55, blue: 0.95) - - private var textPrimary: Color { - colorScheme == .dark ? .white : Color(red: 0.15, green: 0.15, blue: 0.18) - } - - private var textSecondary: Color { - colorScheme == .dark ? Color(white: 0.55) : Color(white: 0.45) - } - - var body: some View { - ScrollView { - VStack(spacing: 20) { - // Date header - dateHeader - .padding(.horizontal, 16) - .padding(.top, 8) - - // Quick add - quickAddButton - .padding(.horizontal, 16) - - // Upcoming section - if !savedTrips.isEmpty { - upcomingSection - } - - // Suggestions - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - suggestionsSection - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 16) - } - - Spacer(minLength: 40) - } - } - .background(bgColor.ignoresSafeArea()) - } - - // MARK: - Date Header - - private var dateHeader: some View { - HStack(alignment: .bottom) { - VStack(alignment: .leading, spacing: 4) { - Text(dayOfWeek) - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(fantasticalRed) - - Text(formattedDate) - .font(.system(size: 28, weight: .bold)) - .foregroundStyle(textPrimary) - } - - Spacer() - - // Today indicator - VStack(spacing: 2) { - Text("TODAY") - .font(.system(size: 9, weight: .bold)) - .foregroundStyle(.white) - - Text(dayNumber) - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(.white) - } - .padding(.horizontal, 10) - .padding(.vertical, 8) - .background( - RoundedRectangle(cornerRadius: 10) - .fill(fantasticalRed) - ) - } - } - - private var dayOfWeek: String { - let formatter = DateFormatter() - formatter.dateFormat = "EEEE" - return formatter.string(from: Date()) - } - - private var formattedDate: String { - let formatter = DateFormatter() - formatter.dateFormat = "MMMM d, yyyy" - return formatter.string(from: Date()) - } - - private var dayNumber: String { - let formatter = DateFormatter() - formatter.dateFormat = "d" - return formatter.string(from: Date()) - } - - // MARK: - Quick Add Button - - private var quickAddButton: some View { - Button { - showNewTrip = true - } label: { - HStack(spacing: 12) { - ZStack { - Circle() - .fill(fantasticalRed) - .frame(width: 28, height: 28) - - Image(systemName: "plus") - .font(.system(size: 14, weight: .bold)) - .foregroundStyle(.white) - } - - Text("Plan New Trip") - .font(.system(size: 16, weight: .medium)) - .foregroundStyle(textPrimary) - - Spacer() - } - .padding(14) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.3 : 0.06), radius: 6, y: 2) - ) - } - .buttonStyle(.plain) - } - - // MARK: - Upcoming Section - - private var upcomingSection: some View { - VStack(alignment: .leading, spacing: 14) { - HStack { - Text("Upcoming") - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("See All") - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(fantasticalBlue) - } - } - .padding(.horizontal, 16) - - VStack(spacing: 2) { - ForEach(savedTrips.prefix(4)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - eventRow(trip) - } - .buttonStyle(.plain) - } - } - } - .background( - RoundedRectangle(cornerRadius: 14) - .fill(cardBg) - ) - .padding(.horizontal, 16) - } - } - - private func eventRow(_ trip: Trip) -> some View { - HStack(spacing: 14) { - // Color bar - RoundedRectangle(cornerRadius: 2) - .fill(trip.uniqueSports.first?.themeColor ?? fantasticalBlue) - .frame(width: 4, height: 44) - - VStack(alignment: .leading, spacing: 3) { - Text(trip.displayName) - .font(.system(size: 15, weight: .medium)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - HStack(spacing: 8) { - Text(trip.formattedDateRange) - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - - Text("•") - .foregroundStyle(textSecondary) - - HStack(spacing: 3) { - Image(systemName: "sportscourt.fill") - .font(.system(size: 10)) - Text("\(trip.totalGames)") - .font(.system(size: 12)) - } - .foregroundStyle(textSecondary) - } - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 12, weight: .medium)) - .foregroundStyle(textSecondary.opacity(0.5)) - } - .padding(.horizontal, 14) - .padding(.vertical, 12) - } - - // MARK: - Suggestions Section - - private var suggestionsSection: some View { - VStack(alignment: .leading, spacing: 14) { - HStack { - Text("Suggested Routes") - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 14)) - .foregroundStyle(fantasticalBlue) - } - } - .padding(.horizontal, 16) - - VStack(spacing: 10) { - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(3)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - suggestionCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - .padding(.horizontal, 16) - } - } - - private func suggestionCard(_ trip: Trip) -> some View { - HStack(spacing: 14) { - // Time block style - VStack(spacing: 2) { - Text("\(trip.stops.count)") - .font(.system(size: 18, weight: .bold)) - .foregroundStyle(textPrimary) - - Text("stops") - .font(.system(size: 10)) - .foregroundStyle(textSecondary) - } - .frame(width: 50) - .padding(.vertical, 10) - .background( - RoundedRectangle(cornerRadius: 8) - .fill(trip.uniqueSports.first?.themeColor.opacity(0.15) ?? fantasticalBlue.opacity(0.15)) - ) - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 15, weight: .medium)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - HStack(spacing: 6) { - if let sport = trip.uniqueSports.first { - HStack(spacing: 3) { - Image(systemName: sport.iconName) - .font(.system(size: 10)) - Text(sport.displayName) - .font(.system(size: 12)) - } - .foregroundStyle(sport.themeColor) - } - - Text("•") - .foregroundStyle(textSecondary) - - Text("\(trip.totalGames) games") - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - } - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 12, weight: .medium)) - .foregroundStyle(textSecondary.opacity(0.5)) - } - .padding(12) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.25 : 0.05), radius: 4, y: 2) - ) - } -} diff --git a/SportsTime/Features/Home/Views/Variants/Flighty/HomeContent_Flighty.swift b/SportsTime/Features/Home/Views/Variants/Flighty/HomeContent_Flighty.swift deleted file mode 100644 index 9608cac..0000000 --- a/SportsTime/Features/Home/Views/Variants/Flighty/HomeContent_Flighty.swift +++ /dev/null @@ -1,391 +0,0 @@ -// -// HomeContent_Flighty.swift -// SportsTime -// -// FLIGHTY-INSPIRED: Aviation dashboard aesthetic. -// Data-rich widgets, clean typography, professional feel. -// Real-time travel data visualization style. -// - -import SwiftUI -import SwiftData - -struct HomeContent_Flighty: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Flighty-inspired colors - private var bgColor: Color { - colorScheme == .dark - ? Color(red: 0.06, green: 0.06, blue: 0.08) - : Color(red: 0.97, green: 0.97, blue: 0.98) - } - - private var cardBg: Color { - colorScheme == .dark - ? Color(red: 0.11, green: 0.11, blue: 0.14) - : Color.white - } - - private var accentBlue: Color { - Color(red: 0.2, green: 0.5, blue: 1.0) - } - - private var textPrimary: Color { - colorScheme == .dark ? .white : Color(red: 0.1, green: 0.1, blue: 0.12) - } - - private var textSecondary: Color { - colorScheme == .dark ? Color(white: 0.55) : Color(white: 0.45) - } - - var body: some View { - ScrollView { - VStack(spacing: 16) { - // Status header - statusHeader - .padding(.horizontal, 20) - .padding(.top, 8) - - // Quick action card - quickActionCard - .padding(.horizontal, 20) - - // Upcoming trips widget - if !savedTrips.isEmpty { - upcomingTripsWidget - .padding(.horizontal, 20) - } - - // Suggested routes - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - suggestedRoutesSection - .padding(.horizontal, 20) - } - - // Stats dashboard - statsDashboard - .padding(.horizontal, 20) - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 20) - } - - Spacer().frame(height: 32) - } - } - .background(bgColor.ignoresSafeArea()) - } - - // MARK: - Status Header - - private var statusHeader: some View { - HStack(spacing: 12) { - VStack(alignment: .leading, spacing: 4) { - Text("Sports Time") - .font(.system(size: 28, weight: .bold, design: .default)) - .foregroundStyle(textPrimary) - - Text(statusText) - .font(.system(size: 13, weight: .medium)) - .foregroundStyle(textSecondary) - } - - Spacer() - - // Live indicator - HStack(spacing: 6) { - Circle() - .fill(Color.green) - .frame(width: 8, height: 8) - Text("LIVE") - .font(.system(size: 11, weight: .bold)) - .foregroundStyle(Color.green) - } - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background( - Capsule() - .fill(Color.green.opacity(0.15)) - ) - } - } - - private var statusText: String { - if savedTrips.isEmpty { - return "No trips planned" - } else { - return "\(savedTrips.count) trip\(savedTrips.count == 1 ? "" : "s") in your schedule" - } - } - - // MARK: - Quick Action Card - - private var quickActionCard: some View { - Button { - showNewTrip = true - } label: { - HStack(spacing: 16) { - ZStack { - Circle() - .fill( - LinearGradient( - colors: [accentBlue, accentBlue.opacity(0.7)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 48, height: 48) - - Image(systemName: "car.fill") - .font(.system(size: 20, weight: .semibold)) - .foregroundStyle(.white) - } - - VStack(alignment: .leading, spacing: 4) { - Text("Plan New Trip") - .font(.system(size: 17, weight: .semibold)) - .foregroundStyle(textPrimary) - - Text("Find games along your route") - .font(.system(size: 13)) - .foregroundStyle(textSecondary) - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 14, weight: .semibold)) - .foregroundStyle(textSecondary) - } - .padding(16) - .background( - RoundedRectangle(cornerRadius: 14) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.3 : 0.06), radius: 8, y: 2) - ) - } - .buttonStyle(.plain) - } - - // MARK: - Upcoming Trips Widget - - private var upcomingTripsWidget: some View { - VStack(alignment: .leading, spacing: 12) { - HStack { - Text("UPCOMING") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(textSecondary) - .tracking(0.5) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("See All") - .font(.system(size: 13, weight: .medium)) - .foregroundStyle(accentBlue) - } - } - - ForEach(savedTrips.prefix(2)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - tripCard(trip) - } - .buttonStyle(.plain) - } - } - } - } - - private func tripCard(_ trip: Trip) -> some View { - HStack(spacing: 14) { - // Departure indicator - VStack(spacing: 4) { - Circle() - .fill(accentBlue) - .frame(width: 10, height: 10) - - Rectangle() - .fill(textSecondary.opacity(0.3)) - .frame(width: 2, height: 30) - - Circle() - .stroke(textSecondary.opacity(0.5), lineWidth: 2) - .frame(width: 10, height: 10) - } - - VStack(alignment: .leading, spacing: 8) { - Text(trip.displayName) - .font(.system(size: 16, weight: .semibold)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - HStack(spacing: 16) { - dataLabel(icon: "calendar", value: trip.formattedDateRange) - dataLabel(icon: "mappin", value: "\(trip.stops.count) stops") - } - } - - Spacer() - - // Time badge - VStack(alignment: .trailing, spacing: 2) { - Text("\(trip.totalGames)") - .font(.system(size: 20, weight: .bold, design: .rounded)) - .foregroundStyle(textPrimary) - Text("games") - .font(.system(size: 11)) - .foregroundStyle(textSecondary) - } - } - .padding(14) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.3 : 0.05), radius: 6, y: 2) - ) - } - - private func dataLabel(icon: String, value: String) -> some View { - HStack(spacing: 4) { - Image(systemName: icon) - .font(.system(size: 11)) - Text(value) - .font(.system(size: 12)) - } - .foregroundStyle(textSecondary) - } - - // MARK: - Suggested Routes Section - - private var suggestedRoutesSection: some View { - VStack(alignment: .leading, spacing: 12) { - HStack { - Text("SUGGESTED ROUTES") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(textSecondary) - .tracking(0.5) - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 13, weight: .medium)) - .foregroundStyle(accentBlue) - } - } - - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(3)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - routeCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - } - - private func routeCard(_ trip: Trip) -> some View { - HStack(spacing: 12) { - // Sport icon - ZStack { - RoundedRectangle(cornerRadius: 8) - .fill(trip.uniqueSports.first?.themeColor.opacity(0.15) ?? accentBlue.opacity(0.15)) - .frame(width: 40, height: 40) - - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt.fill") - .font(.system(size: 16)) - .foregroundStyle(trip.uniqueSports.first?.themeColor ?? accentBlue) - } - - VStack(alignment: .leading, spacing: 3) { - Text(trip.displayName) - .font(.system(size: 15, weight: .medium)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Text("\(trip.stops.count) stops • \(trip.totalGames) games") - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 12, weight: .medium)) - .foregroundStyle(textSecondary.opacity(0.6)) - } - .padding(12) - .background( - RoundedRectangle(cornerRadius: 10) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.25 : 0.04), radius: 4, y: 1) - ) - } - - // MARK: - Stats Dashboard - - private var statsDashboard: some View { - VStack(alignment: .leading, spacing: 12) { - Text("AT A GLANCE") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(textSecondary) - .tracking(0.5) - - HStack(spacing: 12) { - statCard(value: "\(savedTrips.count)", label: "Trips", icon: "map.fill", color: accentBlue) - statCard(value: "\(totalGames)", label: "Games", icon: "sportscourt.fill", color: .orange) - statCard(value: "\(totalStops)", label: "Cities", icon: "building.2.fill", color: .purple) - } - } - } - - private func statCard(value: String, label: String, icon: String, color: Color) -> some View { - VStack(spacing: 8) { - Image(systemName: icon) - .font(.system(size: 18)) - .foregroundStyle(color) - - Text(value) - .font(.system(size: 24, weight: .bold, design: .rounded)) - .foregroundStyle(textPrimary) - - Text(label) - .font(.system(size: 11, weight: .medium)) - .foregroundStyle(textSecondary) - } - .frame(maxWidth: .infinity) - .padding(.vertical, 16) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.25 : 0.04), radius: 4, y: 1) - ) - } - - private var totalGames: Int { - savedTrips.compactMap { $0.trip?.totalGames }.reduce(0, +) - } - - private var totalStops: Int { - savedTrips.compactMap { $0.trip?.stops.count }.reduce(0, +) - } -} diff --git a/SportsTime/Features/Home/Views/Variants/Glassmorphism/HomeContent_Glassmorphism.swift b/SportsTime/Features/Home/Views/Variants/Glassmorphism/HomeContent_Glassmorphism.swift deleted file mode 100644 index 82aebf5..0000000 --- a/SportsTime/Features/Home/Views/Variants/Glassmorphism/HomeContent_Glassmorphism.swift +++ /dev/null @@ -1,344 +0,0 @@ -// -// HomeContent_Glassmorphism.swift -// SportsTime -// -// GLASSMORPHISM: Frosted glass, flowing ethereal shapes. -// Stadium lights blur effect, soft glows, dreamy atmosphere. -// - -import SwiftUI -import SwiftData - -struct HomeContent_Glassmorphism: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Ethereal color palette - private let glowPurple = Color(red: 0.6, green: 0.4, blue: 1.0) - private let glowBlue = Color(red: 0.3, green: 0.6, blue: 1.0) - private let glowPink = Color(red: 1.0, green: 0.5, blue: 0.7) - - var body: some View { - ZStack { - // Gradient background with floating orbs - backgroundLayer - - ScrollView { - VStack(spacing: 24) { - // HERO GLASS CARD - heroGlassCard - .padding(.top, 20) - .padding(.horizontal, 16) - - // FEATURED TRIPS - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - featuredSection - .padding(.horizontal, 16) - } - - // SAVED TRIPS - if !savedTrips.isEmpty { - savedSection - .padding(.horizontal, 16) - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 16) - } - - Spacer(minLength: 32) - } - } - } - } - - // MARK: - Background Layer - - private var backgroundLayer: some View { - ZStack { - // Base gradient - LinearGradient( - colors: colorScheme == .dark - ? [Color(red: 0.1, green: 0.05, blue: 0.2), Color(red: 0.05, green: 0.1, blue: 0.2)] - : [Color(red: 0.95, green: 0.93, blue: 1.0), Color(red: 0.9, green: 0.95, blue: 1.0)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - .ignoresSafeArea() - - // Floating orbs (stadium lights effect) - Circle() - .fill(glowPurple.opacity(0.3)) - .frame(width: 300, height: 300) - .blur(radius: 80) - .offset(x: -100, y: -200) - - Circle() - .fill(glowBlue.opacity(0.3)) - .frame(width: 250, height: 250) - .blur(radius: 70) - .offset(x: 120, y: 100) - - Circle() - .fill(glowPink.opacity(0.2)) - .frame(width: 200, height: 200) - .blur(radius: 60) - .offset(x: -80, y: 400) - } - } - - // MARK: - Hero Glass Card - - private var heroGlassCard: some View { - VStack(alignment: .leading, spacing: 20) { - // Floating label - Text("Welcome to") - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(glowPurple) - - // Title with soft glow - Text("Sports Time") - .font(.system(size: 36, weight: .bold, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white : Color(white: 0.15)) - - Text("Plan your perfect sports road trip with our intelligent route optimizer.") - .font(.system(size: 15, weight: .regular)) - .foregroundStyle(colorScheme == .dark ? .white.opacity(0.7) : Color(white: 0.4)) - .lineSpacing(4) - - // Glass button - Button { - showNewTrip = true - } label: { - HStack(spacing: 12) { - Image(systemName: "sparkles") - .font(.system(size: 16)) - - Text("Start Planning") - .font(.system(size: 16, weight: .semibold, design: .rounded)) - - Spacer() - - Image(systemName: "arrow.right.circle.fill") - .font(.system(size: 20)) - } - .foregroundStyle(.white) - .padding(18) - .background( - LinearGradient( - colors: [glowPurple, glowBlue], - startPoint: .leading, - endPoint: .trailing - ) - ) - .clipShape(RoundedRectangle(cornerRadius: 16)) - .shadow(color: glowPurple.opacity(0.4), radius: 20, x: 0, y: 10) - } - } - .padding(24) - .background(glassBackground) - .clipShape(RoundedRectangle(cornerRadius: 24)) - } - - // MARK: - Glass Background - - private var glassBackground: some View { - ZStack { - // Frosted glass effect - if colorScheme == .dark { - Color.white.opacity(0.08) - } else { - Color.white.opacity(0.6) - } - } - .background(.ultraThinMaterial) - .overlay( - RoundedRectangle(cornerRadius: 24) - .stroke( - LinearGradient( - colors: [ - Color.white.opacity(colorScheme == .dark ? 0.3 : 0.8), - Color.white.opacity(colorScheme == .dark ? 0.1 : 0.3) - ], - startPoint: .topLeading, - endPoint: .bottomTrailing - ), - lineWidth: 1 - ) - ) - } - - // MARK: - Featured Section - - private var featuredSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Text("Featured Trips") - .font(.system(size: 20, weight: .semibold, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white : Color(white: 0.15)) - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 14)) - .foregroundStyle(glowPurple) - .padding(10) - .background(glassCardBackground) - .clipShape(Circle()) - } - } - - // Horizontal scroll of glass cards - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 16) { - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(5)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - glassTripCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - .padding(.vertical, 4) - } - } - } - - private func glassTripCard(_ trip: Trip) -> some View { - VStack(alignment: .leading, spacing: 12) { - // Sport icons with glow - HStack(spacing: 8) { - ForEach(Array(trip.uniqueSports.prefix(3)), id: \.self) { sport in - Image(systemName: sport.iconName) - .font(.system(size: 14)) - .foregroundStyle(sport.themeColor) - .shadow(color: sport.themeColor.opacity(0.5), radius: 8) - } - } - - Text(trip.displayName) - .font(.system(size: 16, weight: .semibold, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white : Color(white: 0.15)) - .lineLimit(2) - - Spacer(minLength: 0) - - HStack { - Label("\(trip.stops.count)", systemImage: "mappin.circle") - Spacer() - Label("\(trip.totalGames)", systemImage: "sportscourt") - } - .font(.system(size: 12, weight: .medium)) - .foregroundStyle(colorScheme == .dark ? .white.opacity(0.6) : Color(white: 0.4)) - } - .padding(16) - .frame(width: 160, height: 140) - .background(glassCardBackground) - .clipShape(RoundedRectangle(cornerRadius: 20)) - .overlay( - RoundedRectangle(cornerRadius: 20) - .stroke(Color.white.opacity(colorScheme == .dark ? 0.15 : 0.5), lineWidth: 1) - ) - } - - private var glassCardBackground: some View { - ZStack { - if colorScheme == .dark { - Color.white.opacity(0.06) - } else { - Color.white.opacity(0.5) - } - } - .background(.ultraThinMaterial) - } - - // MARK: - Saved Section - - private var savedSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Text("Your Trips") - .font(.system(size: 20, weight: .semibold, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white : Color(white: 0.15)) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("See All") - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(glowPurple) - } - } - - VStack(spacing: 12) { - ForEach(savedTrips.prefix(3)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - HStack(spacing: 16) { - // Glow orb - ZStack { - Circle() - .fill( - LinearGradient( - colors: [glowPurple.opacity(0.3), glowBlue.opacity(0.3)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 44, height: 44) - .blur(radius: 4) - - Image(systemName: "map.fill") - .font(.system(size: 16)) - .foregroundStyle(glowPurple) - } - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 15, weight: .medium, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white : Color(white: 0.15)) - - Text("\(trip.stops.count) cities · \(trip.totalGames) games") - .font(.system(size: 12)) - .foregroundStyle(colorScheme == .dark ? .white.opacity(0.5) : Color(white: 0.5)) - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 12, weight: .medium)) - .foregroundStyle(colorScheme == .dark ? .white.opacity(0.3) : Color(white: 0.5)) - } - .padding(16) - .background(glassCardBackground) - .clipShape(RoundedRectangle(cornerRadius: 16)) - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke(Color.white.opacity(colorScheme == .dark ? 0.1 : 0.4), lineWidth: 1) - ) - } - .buttonStyle(.plain) - } - } - } - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/LuxuryEditorial/HomeContent_LuxuryEditorial.swift b/SportsTime/Features/Home/Views/Variants/LuxuryEditorial/HomeContent_LuxuryEditorial.swift deleted file mode 100644 index 697422a..0000000 --- a/SportsTime/Features/Home/Views/Variants/LuxuryEditorial/HomeContent_LuxuryEditorial.swift +++ /dev/null @@ -1,346 +0,0 @@ -// -// HomeContent_LuxuryEditorial.swift -// SportsTime -// -// LUXURY EDITORIAL: Magazine-quality, dramatic typography. -// Elegant serif headlines, cinematic composition, gold accents. -// Premium sports journalism aesthetic. -// - -import SwiftUI -import SwiftData - -struct HomeContent_LuxuryEditorial: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - private let gold = Color(red: 0.85, green: 0.65, blue: 0.13) - - private var bgColor: Color { - colorScheme == .dark ? Color(white: 0.08) : Color(white: 0.98) - } - - private var textPrimary: Color { - colorScheme == .dark ? .white : Color(white: 0.1) - } - - private var textSecondary: Color { - colorScheme == .dark ? Color(white: 0.6) : Color(white: 0.4) - } - - var body: some View { - ScrollView { - VStack(spacing: 0) { - // MASTHEAD - masthead - .padding(.top, 20) - .padding(.bottom, 40) - - // HERO FEATURE - heroFeature - .padding(.horizontal, 24) - .padding(.bottom, 48) - - // EDITORIAL DIVIDER - editorialDivider - .padding(.bottom, 48) - - // FEATURED TRIPS - Magazine Grid - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - featuredSection - .padding(.horizontal, 24) - .padding(.bottom, 48) - } - - // SAVED TRIPS - if !savedTrips.isEmpty { - savedSection - .padding(.horizontal, 24) - .padding(.bottom, 48) - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 24) - .padding(.bottom, 48) - } - - // COLOPHON - colophon - .padding(.bottom, 32) - } - } - .background(bgColor) - } - - // MARK: - Masthead - - private var masthead: some View { - VStack(spacing: 4) { - // Thin rule - Rectangle() - .fill(textSecondary) - .frame(height: 0.5) - .padding(.horizontal, 24) - - HStack { - Text(Date.now.formatted(.dateTime.month(.wide).year())) - .font(.system(size: 10, weight: .medium, design: .serif)) - .tracking(2) - .foregroundStyle(textSecondary) - - Spacer() - - Text("SPORTS TIME") - .font(.system(size: 10, weight: .semibold)) - .tracking(4) - .foregroundStyle(gold) - - Spacer() - - Text("TRAVEL EDITION") - .font(.system(size: 10, weight: .medium, design: .serif)) - .tracking(2) - .foregroundStyle(textSecondary) - } - .padding(.horizontal, 24) - .padding(.vertical, 12) - - // Thin rule - Rectangle() - .fill(textSecondary) - .frame(height: 0.5) - .padding(.horizontal, 24) - } - } - - // MARK: - Hero Feature - - private var heroFeature: some View { - VStack(alignment: .leading, spacing: 20) { - // Kicker - Text("THE JOURNEY BEGINS") - .font(.system(size: 11, weight: .semibold)) - .tracking(3) - .foregroundStyle(gold) - - // Headline - Large serif - Text("Your Ultimate\nSports Road Trip\nAwaits") - .font(.system(size: 42, weight: .regular, design: .serif)) - .foregroundStyle(textPrimary) - .lineSpacing(4) - - // Deck - Text("Meticulously planned routes connecting the greatest stadiums, arenas, and ballparks across America.") - .font(.system(size: 16, weight: .regular, design: .serif)) - .foregroundStyle(textSecondary) - .lineSpacing(6) - .italic() - - // CTA - Elegant button - Button { - showNewTrip = true - } label: { - HStack(spacing: 12) { - Text("Begin Planning") - .font(.system(size: 14, weight: .medium, design: .serif)) - .tracking(1) - - Image(systemName: "arrow.right") - .font(.system(size: 12, weight: .medium)) - } - .foregroundStyle(colorScheme == .dark ? .black : .white) - .padding(.horizontal, 32) - .padding(.vertical, 16) - .background(gold) - } - .padding(.top, 8) - } - } - - // MARK: - Editorial Divider - - private var editorialDivider: some View { - HStack(spacing: 16) { - Rectangle() - .fill(textSecondary.opacity(0.3)) - .frame(height: 0.5) - - Image(systemName: "diamond.fill") - .font(.system(size: 6)) - .foregroundStyle(gold) - - Rectangle() - .fill(textSecondary.opacity(0.3)) - .frame(height: 0.5) - } - .padding(.horizontal, 48) - } - - // MARK: - Featured Section - - private var featuredSection: some View { - VStack(alignment: .leading, spacing: 24) { - // Section header - HStack(alignment: .firstTextBaseline) { - Text("Featured Itineraries") - .font(.system(size: 24, weight: .regular, design: .serif)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 12)) - .foregroundStyle(gold) - } - } - - // Magazine grid - 2 column - LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 20) { - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(4)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - editorialTripCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - } - } - - private func editorialTripCard(_ trip: Trip) -> some View { - VStack(alignment: .leading, spacing: 12) { - // Sport badge - HStack(spacing: 4) { - ForEach(Array(trip.uniqueSports.prefix(2)), id: \.self) { sport in - Text(sport.rawValue.uppercased()) - .font(.system(size: 9, weight: .semibold)) - .tracking(1) - .foregroundStyle(gold) - } - } - - // Title - Text(trip.displayName) - .font(.system(size: 18, weight: .regular, design: .serif)) - .foregroundStyle(textPrimary) - .lineLimit(2) - .multilineTextAlignment(.leading) - - // Stats - Text("\(trip.stops.count) Cities · \(trip.totalGames) Games") - .font(.system(size: 11, weight: .regular, design: .serif)) - .foregroundStyle(textSecondary) - .italic() - - Spacer(minLength: 0) - - // Read more - HStack(spacing: 4) { - Text("View Details") - .font(.system(size: 10, weight: .medium)) - .tracking(1) - Image(systemName: "arrow.right") - .font(.system(size: 8)) - } - .foregroundStyle(gold) - } - .padding(16) - .frame(minHeight: 160) - .overlay( - Rectangle() - .stroke(textSecondary.opacity(0.2), lineWidth: 0.5) - ) - } - - // MARK: - Saved Section - - private var savedSection: some View { - VStack(alignment: .leading, spacing: 20) { - HStack(alignment: .firstTextBaseline) { - Text("Your Collection") - .font(.system(size: 24, weight: .regular, design: .serif)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("View All") - .font(.system(size: 12, weight: .medium)) - .tracking(1) - .foregroundStyle(gold) - } - } - - ForEach(savedTrips.prefix(3)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - HStack(alignment: .top, spacing: 16) { - // Number - Text(String(format: "%02d", (savedTrips.firstIndex(where: { $0.id == savedTrip.id }) ?? 0) + 1)) - .font(.system(size: 11, weight: .regular, design: .serif)) - .foregroundStyle(gold) - .frame(width: 20) - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 16, weight: .regular, design: .serif)) - .foregroundStyle(textPrimary) - - Text(trip.formattedDateRange) - .font(.system(size: 11, weight: .regular, design: .serif)) - .foregroundStyle(textSecondary) - .italic() - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 10)) - .foregroundStyle(textSecondary) - } - .padding(.vertical, 16) - .overlay(alignment: .bottom) { - Rectangle() - .fill(textSecondary.opacity(0.15)) - .frame(height: 0.5) - } - } - .buttonStyle(.plain) - } - } - } - } - - // MARK: - Colophon - - private var colophon: some View { - VStack(spacing: 8) { - Rectangle() - .fill(textSecondary.opacity(0.2)) - .frame(width: 40, height: 0.5) - - Text("SPORTS TIME") - .font(.system(size: 9, weight: .semibold)) - .tracking(4) - .foregroundStyle(textSecondary.opacity(0.5)) - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/MaximalistChaos/HomeContent_MaximalistChaos.swift b/SportsTime/Features/Home/Views/Variants/MaximalistChaos/HomeContent_MaximalistChaos.swift deleted file mode 100644 index ec3d650..0000000 --- a/SportsTime/Features/Home/Views/Variants/MaximalistChaos/HomeContent_MaximalistChaos.swift +++ /dev/null @@ -1,441 +0,0 @@ -// -// HomeContent_MaximalistChaos.swift -// SportsTime -// -// MAXIMALIST CHAOS: More is more. Patterns, gradients, overlapping elements. -// Controlled visual chaos with sports memorabilia collage aesthetic. -// - -import SwiftUI -import SwiftData - -struct HomeContent_MaximalistChaos: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Chaotic color palette - private let hotOrange = Color(red: 1.0, green: 0.4, blue: 0.0) - private let electricPurple = Color(red: 0.6, green: 0.0, blue: 1.0) - private let limeGreen = Color(red: 0.5, green: 1.0, blue: 0.0) - private let hotPink = Color(red: 1.0, green: 0.0, blue: 0.5) - private let skyBlue = Color(red: 0.0, green: 0.7, blue: 1.0) - - var body: some View { - ZStack { - // Chaotic background pattern - chaoticBackground - - ScrollView { - ZStack { - // Floating decorative elements - floatingDecorations - - VStack(spacing: 20) { - // MAXIMALIST HERO - maximalistHero - .padding(.top, 40) - .padding(.horizontal, 16) - - // FEATURED TRIPS - Stacked chaos - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - featuredSection - .padding(.horizontal, 16) - } - - // SAVED TRIPS - if !savedTrips.isEmpty { - savedSection - .padding(.horizontal, 16) - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 16) - } - - Spacer(minLength: 60) - } - } - } - } - } - - // MARK: - Chaotic Background - - private var chaoticBackground: some View { - ZStack { - // Base gradient - LinearGradient( - colors: colorScheme == .dark - ? [Color(red: 0.1, green: 0.0, blue: 0.15), Color(red: 0.0, green: 0.1, blue: 0.15)] - : [Color(red: 1.0, green: 0.95, blue: 0.9), Color(red: 0.95, green: 0.9, blue: 1.0)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - .ignoresSafeArea() - - // Diagonal stripes - GeometryReader { geo in - ForEach(0..<20, id: \.self) { i in - Rectangle() - .fill(hotOrange.opacity(0.05)) - .frame(width: 30, height: geo.size.height * 2) - .rotationEffect(.degrees(45)) - .offset(x: CGFloat(i * 60) - 200) - } - } - .allowsHitTesting(false) - - // Random circles - Circle() - .fill(electricPurple.opacity(0.1)) - .frame(width: 300, height: 300) - .offset(x: 150, y: -100) - - Circle() - .fill(limeGreen.opacity(0.1)) - .frame(width: 200, height: 200) - .offset(x: -100, y: 300) - - Circle() - .fill(hotPink.opacity(0.08)) - .frame(width: 250, height: 250) - .offset(x: 100, y: 500) - } - } - - // MARK: - Floating Decorations - - private var floatingDecorations: some View { - ZStack { - // Sports icons scattered - Image(systemName: "sportscourt.fill") - .font(.system(size: 60)) - .foregroundStyle(hotOrange.opacity(0.15)) - .rotationEffect(.degrees(-15)) - .offset(x: 120, y: 50) - - Image(systemName: "ticket.fill") - .font(.system(size: 50)) - .foregroundStyle(electricPurple.opacity(0.15)) - .rotationEffect(.degrees(20)) - .offset(x: -100, y: 200) - - Image(systemName: "car.fill") - .font(.system(size: 45)) - .foregroundStyle(limeGreen.opacity(0.15)) - .rotationEffect(.degrees(-10)) - .offset(x: 80, y: 400) - - Image(systemName: "map.fill") - .font(.system(size: 55)) - .foregroundStyle(hotPink.opacity(0.12)) - .rotationEffect(.degrees(25)) - .offset(x: -80, y: 600) - } - .allowsHitTesting(false) - } - - // MARK: - Maximalist Hero - - private var maximalistHero: some View { - ZStack { - // Multiple layered backgrounds - RoundedRectangle(cornerRadius: 20) - .fill(electricPurple.opacity(colorScheme == .dark ? 0.3 : 0.15)) - .offset(x: 8, y: 8) - .rotationEffect(.degrees(2)) - - RoundedRectangle(cornerRadius: 20) - .fill(hotOrange.opacity(colorScheme == .dark ? 0.3 : 0.15)) - .offset(x: -4, y: 4) - .rotationEffect(.degrees(-1)) - - // Main card - VStack(spacing: 16) { - // Stacked badges - HStack(spacing: 8) { - ForEach(["🏟️", "🚗", "⚾", "🏀", "🏈"], id: \.self) { emoji in - Text(emoji) - .font(.system(size: 20)) - .padding(8) - .background( - Circle() - .fill(Color.white.opacity(colorScheme == .dark ? 0.1 : 0.8)) - ) - } - } - - // Title with multiple colors - HStack(spacing: 4) { - Text("SPORTS") - .foregroundStyle(hotOrange) - Text("TIME") - .foregroundStyle(electricPurple) - Text("!") - .foregroundStyle(limeGreen) - } - .font(.system(size: 36, weight: .black, design: .rounded)) - - // Subtitle in pill - Text("THE ULTIMATE ROAD TRIP EXPERIENCE") - .font(.system(size: 11, weight: .bold)) - .tracking(2) - .foregroundStyle(.white) - .padding(.horizontal, 16) - .padding(.vertical, 8) - .background( - Capsule() - .fill( - LinearGradient( - colors: [hotPink, electricPurple], - startPoint: .leading, - endPoint: .trailing - ) - ) - ) - - // CTA Button - Maximalist style - Button { - showNewTrip = true - } label: { - ZStack { - // Shadow layers - RoundedRectangle(cornerRadius: 16) - .fill(limeGreen) - .offset(x: 4, y: 4) - - RoundedRectangle(cornerRadius: 16) - .fill(hotPink) - .offset(x: 2, y: 2) - - HStack { - Text("START PLANNING") - .font(.system(size: 16, weight: .black)) - - Image(systemName: "arrow.right.circle.fill") - .font(.system(size: 20)) - } - .foregroundStyle(.black) - .padding(.horizontal, 24) - .padding(.vertical, 16) - .background(hotOrange) - .clipShape(RoundedRectangle(cornerRadius: 16)) - } - } - .padding(.top, 8) - } - .padding(24) - .background( - RoundedRectangle(cornerRadius: 20) - .fill(colorScheme == .dark ? Color.white.opacity(0.1) : Color.white.opacity(0.9)) - .overlay( - RoundedRectangle(cornerRadius: 20) - .stroke( - LinearGradient( - colors: [hotOrange, electricPurple, limeGreen, hotPink], - startPoint: .topLeading, - endPoint: .bottomTrailing - ), - lineWidth: 3 - ) - ) - ) - } - } - - // MARK: - Featured Section - - private var featuredSection: some View { - VStack(alignment: .leading, spacing: 16) { - // Header with multiple elements - HStack { - // Stacked text - VStack(alignment: .leading, spacing: 0) { - Text("FEATURED") - .font(.system(size: 10, weight: .bold)) - .foregroundStyle(hotPink) - Text("TRIPS") - .font(.system(size: 22, weight: .black)) - .foregroundStyle(colorScheme == .dark ? .white : .black) - } - - Spacer() - - // Refresh with chaos - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - ZStack { - Circle() - .fill(limeGreen.opacity(0.3)) - .frame(width: 44, height: 44) - .offset(x: 2, y: 2) - - Image(systemName: "arrow.clockwise") - .font(.system(size: 16, weight: .bold)) - .foregroundStyle(limeGreen) - .padding(12) - .background(Circle().fill(colorScheme == .dark ? Color.white.opacity(0.1) : .white)) - } - } - } - - // Chaotic card stack - ForEach(Array(suggestedTripsGenerator.suggestedTrips.prefix(4).enumerated()), id: \.element.id) { index, suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - chaoticTripCard(suggestedTrip.trip, colorIndex: index) - } - .buttonStyle(.plain) - } - } - } - - private func chaoticTripCard(_ trip: Trip, colorIndex: Int) -> some View { - let colors = [hotOrange, electricPurple, limeGreen, hotPink, skyBlue] - let accentColor = colors[colorIndex % colors.count] - - return ZStack { - // Shadow offset - RoundedRectangle(cornerRadius: 16) - .fill(accentColor.opacity(0.4)) - .offset(x: 4, y: 4) - - HStack(spacing: 14) { - // Sport icon in chaotic circle - ZStack { - Circle() - .fill(accentColor.opacity(0.3)) - .frame(width: 54, height: 54) - .offset(x: 2, y: 2) - - Circle() - .fill(colorScheme == .dark ? Color.white.opacity(0.15) : .white) - .frame(width: 50, height: 50) - - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt") - .font(.system(size: 22)) - .foregroundStyle(accentColor) - } - - VStack(alignment: .leading, spacing: 6) { - Text(trip.displayName) - .font(.system(size: 16, weight: .bold, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white : .black) - .lineLimit(1) - - HStack(spacing: 8) { - Text("\(trip.stops.count) CITIES") - .font(.system(size: 10, weight: .black)) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(accentColor) - .foregroundStyle(.white) - - Text("\(trip.totalGames) GAMES") - .font(.system(size: 10, weight: .black)) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(colors[(colorIndex + 1) % colors.count]) - .foregroundStyle(.white) - } - } - - Spacer() - - Image(systemName: "chevron.right.circle.fill") - .font(.system(size: 24)) - .foregroundStyle(accentColor) - } - .padding(16) - .background( - RoundedRectangle(cornerRadius: 16) - .fill(colorScheme == .dark ? Color.white.opacity(0.08) : Color.white.opacity(0.95)) - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke(accentColor.opacity(0.5), lineWidth: 2) - ) - ) - } - } - - // MARK: - Saved Section - - private var savedSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - VStack(alignment: .leading, spacing: 0) { - Text("YOUR") - .font(.system(size: 10, weight: .bold)) - .foregroundStyle(skyBlue) - Text("TRIPS") - .font(.system(size: 22, weight: .black)) - .foregroundStyle(colorScheme == .dark ? .white : .black) - } - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("VIEW ALL →") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(skyBlue) - } - } - - ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - let colors = [skyBlue, hotOrange, electricPurple] - let accentColor = colors[index % colors.count] - - HStack { - Rectangle() - .fill(accentColor) - .frame(width: 6) - - Text(trip.displayName) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white : .black) - - Spacer() - - Text("\(trip.stops.count)") - .font(.system(size: 14, weight: .black)) - .foregroundStyle(.white) - .padding(.horizontal, 12) - .padding(.vertical, 6) - .background(accentColor) - .clipShape(Capsule()) - } - .padding(.vertical, 14) - .padding(.horizontal, 12) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(colorScheme == .dark ? Color.white.opacity(0.05) : Color.white.opacity(0.8)) - .overlay( - RoundedRectangle(cornerRadius: 12) - .stroke(accentColor.opacity(0.3), lineWidth: 1) - ) - ) - } - .buttonStyle(.plain) - } - } - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/NeoBrutalist/HomeContent_NeoBrutalist.swift b/SportsTime/Features/Home/Views/Variants/NeoBrutalist/HomeContent_NeoBrutalist.swift deleted file mode 100644 index 482da8f..0000000 --- a/SportsTime/Features/Home/Views/Variants/NeoBrutalist/HomeContent_NeoBrutalist.swift +++ /dev/null @@ -1,327 +0,0 @@ -// -// HomeContent_NeoBrutalist.swift -// SportsTime -// -// NEO-BRUTALIST: Bold blocks, harsh shadows, high contrast. -// Offset elements, thick borders, punchy colors. -// Ticket stub aesthetic with hard edges. -// - -import SwiftUI -import SwiftData - -struct HomeContent_NeoBrutalist: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Neo-brutalist palette - private let primaryYellow = Color(red: 1.0, green: 0.9, blue: 0.0) - private let hotPink = Color(red: 1.0, green: 0.2, blue: 0.6) - private let electricBlue = Color(red: 0.2, green: 0.4, blue: 1.0) - - private var bgColor: Color { - colorScheme == .dark ? Color(white: 0.08) : Color(white: 0.95) - } - - private var textColor: Color { - colorScheme == .dark ? .white : .black - } - - var body: some View { - ScrollView { - VStack(spacing: 24) { - // HERO BLOCK with offset shadow - heroBlock - .padding(.top, 16) - .padding(.horizontal, 16) - - // ACTION BUTTONS - Bold stacked blocks - actionBlocks - .padding(.horizontal, 16) - - // FEATURED TRIPS - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - featuredSection - .padding(.horizontal, 16) - } - - // SAVED TRIPS - if !savedTrips.isEmpty { - savedSection - .padding(.horizontal, 16) - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 16) - } - - Spacer(minLength: 32) - } - } - .background(bgColor) - } - - // MARK: - Hero Block - - private var heroBlock: some View { - ZStack { - // Shadow block (offset) - RoundedRectangle(cornerRadius: 0) - .fill(textColor) - .offset(x: 8, y: 8) - - // Main block - VStack(alignment: .leading, spacing: 16) { - // Badge - Text("SPORTS TRIP PLANNER") - .font(.system(size: 10, weight: .black)) - .tracking(2) - .foregroundStyle(bgColor) - .padding(.horizontal, 12) - .padding(.vertical, 6) - .background(primaryYellow) - - // Main title - VStack(alignment: .leading, spacing: 4) { - Text("PLAN YOUR") - .font(.system(size: 32, weight: .black)) - .foregroundStyle(textColor) - - HStack(spacing: 0) { - Text("ROAD") - .font(.system(size: 32, weight: .black)) - .foregroundStyle(hotPink) - Text(" TRIP") - .font(.system(size: 32, weight: .black)) - .foregroundStyle(textColor) - } - } - - // Description - Text("Hit every stadium. Catch every game. Make memories that last.") - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(textColor.opacity(0.7)) - } - .padding(24) - .frame(maxWidth: .infinity, alignment: .leading) - .background(bgColor) - .border(textColor, width: 3) - } - } - - // MARK: - Action Blocks - - private var actionBlocks: some View { - VStack(spacing: 12) { - // Primary CTA - Button { - showNewTrip = true - } label: { - ZStack { - // Shadow - Rectangle() - .fill(textColor) - .offset(x: 6, y: 6) - - HStack { - Text("START PLANNING") - .font(.system(size: 16, weight: .black)) - .tracking(1) - - Spacer() - - Text("→") - .font(.system(size: 24, weight: .black)) - } - .foregroundStyle(.black) - .padding(20) - .background(primaryYellow) - .border(.black, width: 3) - } - } - .frame(height: 70) - - // Secondary actions - HStack(spacing: 12) { - Button { - selectedTab = 1 - } label: { - secondaryBlock(text: "SCHEDULE", color: electricBlue) - } - - Button { - selectedTab = 2 - } label: { - secondaryBlock(text: "MY TRIPS", color: hotPink) - } - } - } - } - - private func secondaryBlock(text: String, color: Color) -> some View { - ZStack { - Rectangle() - .fill(textColor) - .offset(x: 4, y: 4) - - Text(text) - .font(.system(size: 12, weight: .black)) - .tracking(1) - .foregroundStyle(.white) - .padding(16) - .frame(maxWidth: .infinity) - .background(color) - .border(.black, width: 2) - } - .frame(height: 56) - } - - // MARK: - Featured Section - - private var featuredSection: some View { - VStack(alignment: .leading, spacing: 16) { - // Section header - HStack { - Text("FEATURED") - .font(.system(size: 12, weight: .black)) - .tracking(2) - .foregroundStyle(textColor) - - Rectangle() - .fill(primaryYellow) - .frame(height: 4) - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 14, weight: .bold)) - .foregroundStyle(textColor) - } - } - - // Trip cards - stacked blocks - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(4)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - neoBrutalistTripCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - } - - private func neoBrutalistTripCard(_ trip: Trip) -> some View { - ZStack { - // Shadow - Rectangle() - .fill(textColor) - .offset(x: 5, y: 5) - - HStack(spacing: 16) { - // Color block indicator - Rectangle() - .fill(trip.uniqueSports.first?.themeColor ?? primaryYellow) - .frame(width: 8) - - VStack(alignment: .leading, spacing: 6) { - Text(trip.displayName.uppercased()) - .font(.system(size: 14, weight: .black)) - .foregroundStyle(textColor) - .lineLimit(1) - - HStack(spacing: 12) { - Text("\(trip.stops.count) CITIES") - .font(.system(size: 10, weight: .bold)) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(primaryYellow) - .foregroundStyle(.black) - - Text("\(trip.totalGames) GAMES") - .font(.system(size: 10, weight: .bold)) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(hotPink) - .foregroundStyle(.white) - } - } - - Spacer() - - Text("→") - .font(.system(size: 18, weight: .black)) - .foregroundStyle(textColor) - } - .padding(16) - .background(bgColor) - .border(textColor, width: 2) - } - } - - // MARK: - Saved Section - - private var savedSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Text("YOUR TRIPS") - .font(.system(size: 12, weight: .black)) - .tracking(2) - .foregroundStyle(textColor) - - Rectangle() - .fill(hotPink) - .frame(height: 4) - - Button { - selectedTab = 2 - } label: { - Text("ALL →") - .font(.system(size: 12, weight: .black)) - .foregroundStyle(hotPink) - } - } - - ForEach(savedTrips.prefix(3)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - HStack { - Rectangle() - .fill(electricBlue) - .frame(width: 4) - - Text(trip.displayName.uppercased()) - .font(.system(size: 13, weight: .bold)) - .foregroundStyle(textColor) - - Spacer() - - Text("\(trip.stops.count)") - .font(.system(size: 12, weight: .black)) - .foregroundStyle(.black) - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background(primaryYellow) - } - .padding(12) - .border(textColor.opacity(0.3), width: 2) - } - .buttonStyle(.plain) - } - } - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/NikeRunClub/HomeContent_NikeRunClub.swift b/SportsTime/Features/Home/Views/Variants/NikeRunClub/HomeContent_NikeRunClub.swift deleted file mode 100644 index d02a1a0..0000000 --- a/SportsTime/Features/Home/Views/Variants/NikeRunClub/HomeContent_NikeRunClub.swift +++ /dev/null @@ -1,304 +0,0 @@ -// -// HomeContent_NikeRunClub.swift -// SportsTime -// -// NIKE RUN CLUB-INSPIRED: Athletic, bold stats. -// Dynamic feel, activity-focused design. -// Black/white with vibrant accents. -// - -import SwiftUI -import SwiftData - -struct HomeContent_NikeRunClub: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Nike-inspired colors - private var bgColor: Color { - colorScheme == .dark - ? Color.black - : Color.white - } - - private let nikeVolt = Color(red: 0.77, green: 1.0, blue: 0.0) - private let nikeOrange = Color(red: 1.0, green: 0.35, blue: 0.0) - - private var textPrimary: Color { - colorScheme == .dark ? .white : .black - } - - private var textSecondary: Color { - colorScheme == .dark ? Color(white: 0.5) : Color(white: 0.45) - } - - var body: some View { - ScrollView { - VStack(spacing: 24) { - // Hero stats - heroStats - .padding(.horizontal, 20) - .padding(.top, 16) - - // Start activity button - startButton - .padding(.horizontal, 20) - - // Activity feed - if !savedTrips.isEmpty { - activityFeed - } - - // Challenges/suggestions - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - challengesSection - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 20) - } - - Spacer(minLength: 50) - } - } - .background(bgColor.ignoresSafeArea()) - } - - // MARK: - Hero Stats - - private var heroStats: some View { - VStack(spacing: 8) { - Text("SPORTS TIME") - .font(.system(size: 11, weight: .bold)) - .foregroundStyle(textSecondary) - .tracking(2) - - Text("\(savedTrips.count)") - .font(.system(size: 72, weight: .bold)) - .foregroundStyle(textPrimary) - - Text("TRIPS PLANNED") - .font(.system(size: 13, weight: .bold)) - .foregroundStyle(textSecondary) - .tracking(1) - - // Stats row - HStack(spacing: 32) { - statItem(value: "\(totalGames)", label: "GAMES") - statItem(value: "\(totalStops)", label: "CITIES") - statItem(value: "\(uniqueSports)", label: "SPORTS") - } - .padding(.top, 20) - } - .frame(maxWidth: .infinity) - .padding(.vertical, 32) - } - - private func statItem(value: String, label: String) -> some View { - VStack(spacing: 4) { - Text(value) - .font(.system(size: 28, weight: .bold)) - .foregroundStyle(textPrimary) - - Text(label) - .font(.system(size: 10, weight: .semibold)) - .foregroundStyle(textSecondary) - .tracking(0.5) - } - } - - private var totalGames: Int { - savedTrips.compactMap { $0.trip?.totalGames }.reduce(0, +) - } - - private var totalStops: Int { - savedTrips.compactMap { $0.trip?.stops.count }.reduce(0, +) - } - - private var uniqueSports: Int { - Set(savedTrips.flatMap { $0.trip?.uniqueSports ?? [] }).count - } - - // MARK: - Start Button - - private var startButton: some View { - Button { - showNewTrip = true - } label: { - HStack { - Text("START") - .font(.system(size: 18, weight: .bold)) - .tracking(1) - } - .foregroundStyle(.black) - .frame(maxWidth: .infinity) - .padding(.vertical, 18) - .background( - RoundedRectangle(cornerRadius: 30) - .fill(nikeVolt) - ) - } - .buttonStyle(.plain) - } - - // MARK: - Activity Feed - - private var activityFeed: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Text("ACTIVITY") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(textSecondary) - .tracking(1) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("See All") - .font(.system(size: 13, weight: .semibold)) - .foregroundStyle(textPrimary) - } - } - .padding(.horizontal, 20) - - VStack(spacing: 0) { - ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - activityRow(trip, index: index) - } - .buttonStyle(.plain) - } - } - } - } - } - - private func activityRow(_ trip: Trip, index: Int) -> some View { - HStack(spacing: 16) { - // Activity type indicator - ZStack { - Circle() - .fill(index == 0 ? nikeVolt : nikeOrange) - .frame(width: 44, height: 44) - - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt.fill") - .font(.system(size: 18, weight: .semibold)) - .foregroundStyle(.black) - } - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 16, weight: .semibold)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Text(trip.formattedDateRange) - .font(.system(size: 13)) - .foregroundStyle(textSecondary) - } - - Spacer() - - VStack(alignment: .trailing, spacing: 2) { - Text("\(trip.totalGames)") - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(textPrimary) - - Text("games") - .font(.system(size: 11)) - .foregroundStyle(textSecondary) - } - } - .padding(.horizontal, 20) - .padding(.vertical, 14) - .background( - Rectangle() - .fill(colorScheme == .dark ? Color(white: 0.08) : Color(white: 0.97)) - ) - } - - // MARK: - Challenges Section - - private var challengesSection: some View { - VStack(alignment: .leading, spacing: 16) { - Text("SUGGESTED ROUTES") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(textSecondary) - .tracking(1) - .padding(.horizontal, 20) - - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 14) { - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(4)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - challengeCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - .padding(.horizontal, 20) - } - } - } - - private func challengeCard(_ trip: Trip) -> some View { - VStack(alignment: .leading, spacing: 12) { - // Challenge header - ZStack(alignment: .topLeading) { - RoundedRectangle(cornerRadius: 12) - .fill( - LinearGradient( - colors: [nikeOrange, nikeOrange.opacity(0.7)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(height: 100) - - VStack(alignment: .leading, spacing: 4) { - Text("CHALLENGE") - .font(.system(size: 10, weight: .bold)) - .foregroundStyle(.white.opacity(0.8)) - .tracking(1) - - Text("\(trip.totalGames) GAMES") - .font(.system(size: 22, weight: .bold)) - .foregroundStyle(.white) - } - .padding(14) - } - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 14, weight: .semibold)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Text("\(trip.stops.count) cities") - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - } - } - .frame(width: 180) - .padding(12) - .background( - RoundedRectangle(cornerRadius: 14) - .fill(colorScheme == .dark ? Color(white: 0.1) : Color(white: 0.96)) - ) - } -} diff --git a/SportsTime/Features/Home/Views/Variants/Organic/HomeContent_Organic.swift b/SportsTime/Features/Home/Views/Variants/Organic/HomeContent_Organic.swift deleted file mode 100644 index 34f868a..0000000 --- a/SportsTime/Features/Home/Views/Variants/Organic/HomeContent_Organic.swift +++ /dev/null @@ -1,320 +0,0 @@ -// -// HomeContent_Organic.swift -// SportsTime -// -// ORGANIC: Soft curves, earthy tones, breathing life. -// Natural stadium grass vibes, flowing shapes, gentle animations. -// - -import SwiftUI -import SwiftData - -struct HomeContent_Organic: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Earthy organic palette - private let leafGreen = Color(red: 0.35, green: 0.65, blue: 0.35) - private let earthBrown = Color(red: 0.55, green: 0.4, blue: 0.3) - private let warmSand = Color(red: 0.95, green: 0.9, blue: 0.8) - private let skyBlue = Color(red: 0.6, green: 0.8, blue: 0.95) - - private var bgColor: Color { - colorScheme == .dark - ? Color(red: 0.1, green: 0.12, blue: 0.1) - : warmSand - } - - private var textPrimary: Color { - colorScheme == .dark ? Color(white: 0.9) : earthBrown - } - - private var textSecondary: Color { - colorScheme == .dark ? Color(white: 0.6) : earthBrown.opacity(0.7) - } - - var body: some View { - ScrollView { - VStack(spacing: 28) { - // ORGANIC HERO - organicHero - .padding(.top, 16) - .padding(.horizontal, 20) - - // FEATURED TRIPS - Flowing cards - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - featuredSection - .padding(.horizontal, 20) - } - - // SAVED TRIPS - if !savedTrips.isEmpty { - savedSection - .padding(.horizontal, 20) - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 20) - } - - // FOOTER LEAF - footerLeaf - .padding(.bottom, 32) - } - } - .background(bgColor) - } - - // MARK: - Organic Hero - - private var organicHero: some View { - VStack(spacing: 20) { - // Organic shape header - ZStack { - // Background blob - Ellipse() - .fill(leafGreen.opacity(0.15)) - .frame(width: 280, height: 140) - .rotationEffect(.degrees(-5)) - - Ellipse() - .fill(skyBlue.opacity(0.1)) - .frame(width: 200, height: 100) - .offset(x: 60, y: 20) - .rotationEffect(.degrees(10)) - - VStack(spacing: 8) { - // Leaf icon - Image(systemName: "leaf.fill") - .font(.system(size: 28)) - .foregroundStyle(leafGreen) - - Text("Sports Time") - .font(.system(size: 32, weight: .semibold, design: .rounded)) - .foregroundStyle(textPrimary) - - Text("Journey naturally") - .font(.system(size: 14, weight: .medium, design: .rounded)) - .foregroundStyle(textSecondary) - .italic() - } - } - .padding(.vertical, 20) - - // Description in organic container - Text("Let your sports adventure unfold organically. We'll guide you through the most scenic routes connecting America's greatest stadiums.") - .font(.system(size: 15, weight: .regular, design: .rounded)) - .foregroundStyle(textSecondary) - .multilineTextAlignment(.center) - .lineSpacing(6) - .padding(.horizontal, 16) - - // Organic CTA button - Button { - showNewTrip = true - } label: { - HStack(spacing: 12) { - Image(systemName: "arrow.right.circle") - .font(.system(size: 18)) - - Text("Begin Your Journey") - .font(.system(size: 16, weight: .semibold, design: .rounded)) - } - .foregroundStyle(.white) - .padding(.horizontal, 32) - .padding(.vertical, 16) - .background( - Capsule() - .fill(leafGreen) - ) - .shadow(color: leafGreen.opacity(0.3), radius: 15, y: 8) - } - } - .padding(24) - .background( - RoundedRectangle(cornerRadius: 32) - .fill(colorScheme == .dark ? Color.white.opacity(0.05) : Color.white.opacity(0.7)) - ) - } - - // MARK: - Featured Section - - private var featuredSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Image(systemName: "leaf.fill") - .font(.system(size: 14)) - .foregroundStyle(leafGreen) - - Text("Featured Journeys") - .font(.system(size: 18, weight: .semibold, design: .rounded)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.triangle.2.circlepath") - .font(.system(size: 14)) - .foregroundStyle(leafGreen) - } - } - - // Organic flowing cards - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(4)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - organicTripCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - } - - private func organicTripCard(_ trip: Trip) -> some View { - HStack(spacing: 16) { - // Organic circle with sport color - ZStack { - Circle() - .fill(trip.uniqueSports.first?.themeColor.opacity(0.2) ?? leafGreen.opacity(0.2)) - .frame(width: 56, height: 56) - - Circle() - .fill(trip.uniqueSports.first?.themeColor.opacity(0.3) ?? leafGreen.opacity(0.3)) - .frame(width: 40, height: 40) - - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt") - .font(.system(size: 18)) - .foregroundStyle(trip.uniqueSports.first?.themeColor ?? leafGreen) - } - - VStack(alignment: .leading, spacing: 6) { - Text(trip.displayName) - .font(.system(size: 16, weight: .semibold, design: .rounded)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - HStack(spacing: 12) { - Label("\(trip.stops.count) stops", systemImage: "mappin.circle") - Label("\(trip.totalGames) games", systemImage: "sportscourt") - } - .font(.system(size: 12, weight: .medium, design: .rounded)) - .foregroundStyle(textSecondary) - } - - Spacer() - - Image(systemName: "chevron.right.circle") - .font(.system(size: 20)) - .foregroundStyle(leafGreen.opacity(0.6)) - } - .padding(16) - .background( - RoundedRectangle(cornerRadius: 24) - .fill(colorScheme == .dark ? Color.white.opacity(0.05) : Color.white.opacity(0.8)) - ) - } - - // MARK: - Saved Section - - private var savedSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Image(systemName: "tree.fill") - .font(.system(size: 14)) - .foregroundStyle(earthBrown) - - Text("Your Journeys") - .font(.system(size: 18, weight: .semibold, design: .rounded)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("View all") - .font(.system(size: 14, weight: .medium, design: .rounded)) - .foregroundStyle(leafGreen) - } - } - - ForEach(savedTrips.prefix(3)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - HStack(spacing: 14) { - // Organic dot cluster - ZStack { - Circle() - .fill(earthBrown.opacity(0.2)) - .frame(width: 8, height: 8) - .offset(x: -6, y: -4) - Circle() - .fill(leafGreen.opacity(0.3)) - .frame(width: 10, height: 10) - .offset(x: 4, y: 2) - Circle() - .fill(skyBlue.opacity(0.3)) - .frame(width: 6, height: 6) - .offset(x: -2, y: 6) - } - .frame(width: 24, height: 24) - - VStack(alignment: .leading, spacing: 2) { - Text(trip.displayName) - .font(.system(size: 15, weight: .medium, design: .rounded)) - .foregroundStyle(textPrimary) - - Text(trip.formattedDateRange) - .font(.system(size: 12, design: .rounded)) - .foregroundStyle(textSecondary) - } - - Spacer() - - Capsule() - .fill(leafGreen.opacity(0.15)) - .frame(width: 40, height: 24) - .overlay( - Text("\(trip.stops.count)") - .font(.system(size: 12, weight: .semibold, design: .rounded)) - .foregroundStyle(leafGreen) - ) - } - .padding(.vertical, 12) - } - .buttonStyle(.plain) - } - } - } - } - - // MARK: - Footer Leaf - - private var footerLeaf: some View { - VStack(spacing: 8) { - Image(systemName: "leaf.fill") - .font(.system(size: 16)) - .foregroundStyle(leafGreen.opacity(0.4)) - .rotationEffect(.degrees(45)) - - Text("Sports Time") - .font(.system(size: 11, weight: .medium, design: .rounded)) - .foregroundStyle(textSecondary.opacity(0.5)) - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/Playful/HomeContent_Playful.swift b/SportsTime/Features/Home/Views/Variants/Playful/HomeContent_Playful.swift deleted file mode 100644 index 9b3c15f..0000000 --- a/SportsTime/Features/Home/Views/Variants/Playful/HomeContent_Playful.swift +++ /dev/null @@ -1,378 +0,0 @@ -// -// HomeContent_Playful.swift -// SportsTime -// -// PLAYFUL: Bouncy, toy-like, rounded everything. -// Bright candy colors, wobbly shapes, fun animations. -// Sports meets playground aesthetic. -// - -import SwiftUI -import SwiftData - -struct HomeContent_Playful: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Candy color palette - private let candyPink = Color(red: 1.0, green: 0.4, blue: 0.6) - private let candyBlue = Color(red: 0.4, green: 0.7, blue: 1.0) - private let candyYellow = Color(red: 1.0, green: 0.85, blue: 0.3) - private let candyGreen = Color(red: 0.4, green: 0.9, blue: 0.6) - private let candyPurple = Color(red: 0.7, green: 0.5, blue: 1.0) - - private var bgColor: Color { - colorScheme == .dark - ? Color(red: 0.12, green: 0.1, blue: 0.18) - : Color(red: 0.98, green: 0.97, blue: 1.0) - } - - var body: some View { - ZStack { - bgColor.ignoresSafeArea() - - // Floating blobs - floatingBlobs - - ScrollView { - VStack(spacing: 28) { - // PLAYFUL HERO - playfulHero - .padding(.top, 20) - .padding(.horizontal, 20) - - // FEATURED TRIPS - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - featuredSection - .padding(.horizontal, 20) - } - - // SAVED TRIPS - if !savedTrips.isEmpty { - savedSection - .padding(.horizontal, 20) - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 20) - } - - // FUN FOOTER - funFooter - .padding(.bottom, 32) - } - } - } - } - - // MARK: - Floating Blobs - - private var floatingBlobs: some View { - ZStack { - Ellipse() - .fill(candyPink.opacity(0.15)) - .frame(width: 200, height: 160) - .rotationEffect(.degrees(-20)) - .offset(x: -100, y: -150) - - Ellipse() - .fill(candyBlue.opacity(0.15)) - .frame(width: 180, height: 140) - .rotationEffect(.degrees(15)) - .offset(x: 120, y: 100) - - Ellipse() - .fill(candyYellow.opacity(0.12)) - .frame(width: 150, height: 120) - .rotationEffect(.degrees(-10)) - .offset(x: -60, y: 400) - - Ellipse() - .fill(candyGreen.opacity(0.1)) - .frame(width: 130, height: 100) - .rotationEffect(.degrees(25)) - .offset(x: 100, y: 550) - } - .allowsHitTesting(false) - } - - // MARK: - Playful Hero - - private var playfulHero: some View { - VStack(spacing: 20) { - // Bouncy mascot area - ZStack { - // Background wobble - RoundedRectangle(cornerRadius: 40) - .fill(candyYellow.opacity(0.3)) - .frame(width: 140, height: 100) - .rotationEffect(.degrees(-5)) - - RoundedRectangle(cornerRadius: 40) - .fill(candyPink.opacity(0.3)) - .frame(width: 120, height: 80) - .offset(x: 30, y: 10) - .rotationEffect(.degrees(5)) - - // Sports emojis - HStack(spacing: 12) { - Text("⚾") - .font(.system(size: 32)) - .rotationEffect(.degrees(-10)) - Text("🏀") - .font(.system(size: 36)) - Text("🏈") - .font(.system(size: 32)) - .rotationEffect(.degrees(10)) - } - } - .padding(.vertical, 16) - - // Title with bounce - VStack(spacing: 4) { - Text("Sports Time!") - .font(.system(size: 34, weight: .heavy, design: .rounded)) - .foregroundStyle( - LinearGradient( - colors: [candyPink, candyPurple], - startPoint: .leading, - endPoint: .trailing - ) - ) - - Text("Your adventure starts here") - .font(.system(size: 15, weight: .medium, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? Color.white.opacity(0.7) : Color(white: 0.4)) - } - - // Playful CTA - Button { - showNewTrip = true - } label: { - HStack(spacing: 12) { - Text("Let's Go!") - .font(.system(size: 18, weight: .bold, design: .rounded)) - - Image(systemName: "arrow.right.circle.fill") - .font(.system(size: 22)) - } - .foregroundStyle(.white) - .padding(.horizontal, 36) - .padding(.vertical, 18) - .background( - Capsule() - .fill( - LinearGradient( - colors: [candyPink, candyPurple], - startPoint: .leading, - endPoint: .trailing - ) - ) - ) - .shadow(color: candyPink.opacity(0.4), radius: 20, y: 10) - } - } - .padding(28) - .background( - RoundedRectangle(cornerRadius: 36) - .fill(colorScheme == .dark ? Color.white.opacity(0.08) : Color.white.opacity(0.9)) - .shadow(color: Color.black.opacity(0.08), radius: 20, y: 8) - ) - } - - // MARK: - Featured Section - - private var featuredSection: some View { - VStack(alignment: .leading, spacing: 18) { - HStack { - Text("Cool Trips") - .font(.system(size: 22, weight: .bold, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white : Color(white: 0.15)) - - Text("✨") - .font(.system(size: 18)) - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 16, weight: .medium)) - .foregroundStyle(candyBlue) - .padding(12) - .background( - Circle() - .fill(candyBlue.opacity(0.15)) - ) - } - } - - ForEach(Array(suggestedTripsGenerator.suggestedTrips.prefix(4).enumerated()), id: \.element.id) { index, suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - playfulTripCard(suggestedTrip.trip, colorIndex: index) - } - .buttonStyle(.plain) - } - } - } - - private func playfulTripCard(_ trip: Trip, colorIndex: Int) -> some View { - let colors = [candyPink, candyBlue, candyGreen, candyPurple, candyYellow] - let accentColor = colors[colorIndex % colors.count] - - return HStack(spacing: 16) { - // Bouncy icon - ZStack { - Circle() - .fill(accentColor.opacity(0.2)) - .frame(width: 56, height: 56) - - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt.fill") - .font(.system(size: 24)) - .foregroundStyle(accentColor) - } - - VStack(alignment: .leading, spacing: 6) { - Text(trip.displayName) - .font(.system(size: 16, weight: .semibold, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white : Color(white: 0.15)) - .lineLimit(1) - - HStack(spacing: 10) { - HStack(spacing: 4) { - Image(systemName: "mappin.circle.fill") - .font(.system(size: 12)) - Text("\(trip.stops.count)") - .font(.system(size: 13, weight: .medium, design: .rounded)) - } - .foregroundStyle(accentColor) - - HStack(spacing: 4) { - Image(systemName: "sportscourt.fill") - .font(.system(size: 12)) - Text("\(trip.totalGames)") - .font(.system(size: 13, weight: .medium, design: .rounded)) - } - .foregroundStyle(colorScheme == .dark ? .white.opacity(0.6) : Color(white: 0.5)) - } - } - - Spacer() - - Image(systemName: "chevron.right.circle.fill") - .font(.system(size: 24)) - .foregroundStyle(accentColor.opacity(0.5)) - } - .padding(16) - .background( - RoundedRectangle(cornerRadius: 24) - .fill(colorScheme == .dark ? Color.white.opacity(0.06) : Color.white.opacity(0.9)) - .shadow(color: accentColor.opacity(0.15), radius: 12, y: 4) - ) - } - - // MARK: - Saved Section - - private var savedSection: some View { - VStack(alignment: .leading, spacing: 18) { - HStack { - Text("Your Trips") - .font(.system(size: 22, weight: .bold, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white : Color(white: 0.15)) - - Text("🎒") - .font(.system(size: 18)) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("See all") - .font(.system(size: 14, weight: .medium, design: .rounded)) - .foregroundStyle(candyPurple) - } - } - - ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - let colors = [candyYellow, candyGreen, candyBlue] - let accentColor = colors[index % colors.count] - - HStack(spacing: 14) { - // Number bubble - Text("\(index + 1)") - .font(.system(size: 14, weight: .bold, design: .rounded)) - .foregroundStyle(.white) - .frame(width: 32, height: 32) - .background( - Circle() - .fill(accentColor) - ) - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 15, weight: .medium, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white : Color(white: 0.15)) - - Text(trip.formattedDateRange) - .font(.system(size: 12, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white.opacity(0.5) : Color(white: 0.5)) - } - - Spacer() - - Capsule() - .fill(accentColor.opacity(0.2)) - .frame(width: 44, height: 28) - .overlay( - Text("\(trip.stops.count)") - .font(.system(size: 13, weight: .semibold, design: .rounded)) - .foregroundStyle(accentColor) - ) - } - .padding(14) - .background( - RoundedRectangle(cornerRadius: 20) - .fill(colorScheme == .dark ? Color.white.opacity(0.05) : Color.white.opacity(0.85)) - ) - } - .buttonStyle(.plain) - } - } - } - } - - // MARK: - Fun Footer - - private var funFooter: some View { - VStack(spacing: 8) { - HStack(spacing: 8) { - Text("🏟️") - Text("🚗") - Text("🎉") - } - .font(.system(size: 16)) - - Text("Sports Time") - .font(.system(size: 12, weight: .medium, design: .rounded)) - .foregroundStyle(colorScheme == .dark ? .white.opacity(0.4) : Color(white: 0.5)) - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/RetroFuturism/HomeContent_RetroFuturism.swift b/SportsTime/Features/Home/Views/Variants/RetroFuturism/HomeContent_RetroFuturism.swift deleted file mode 100644 index 49077ba..0000000 --- a/SportsTime/Features/Home/Views/Variants/RetroFuturism/HomeContent_RetroFuturism.swift +++ /dev/null @@ -1,363 +0,0 @@ -// -// HomeContent_RetroFuturism.swift -// SportsTime -// -// RETRO-FUTURISM: 80s sci-fi meets modern sports tech. -// Neon colors, CRT effects, chrome accents, sports broadcast graphics. -// - -import SwiftUI -import SwiftData - -struct HomeContent_RetroFuturism: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Retro color palette - private let neonCyan = Color(red: 0.0, green: 1.0, blue: 0.9) - private let neonMagenta = Color(red: 1.0, green: 0.0, blue: 0.8) - private let neonYellow = Color(red: 1.0, green: 1.0, blue: 0.0) - private let darkBg = Color(red: 0.05, green: 0.0, blue: 0.15) - private let chrome = Color(white: 0.85) - - var body: some View { - ZStack { - // Deep dark background - darkBg.ignoresSafeArea() - - // Scan lines overlay - scanLinesOverlay - - ScrollView { - VStack(spacing: 32) { - // RETRO HEADER - retroHeader - .padding(.top, 24) - - // NEON CTA BLOCK - neonCTABlock - .padding(.horizontal, 16) - - // FEATURED TRIPS - Broadcast style - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - featuredSection - .padding(.horizontal, 16) - } - - // SAVED TRIPS - if !savedTrips.isEmpty { - savedSection - .padding(.horizontal, 16) - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 16) - } - - // RETRO FOOTER - retroFooter - .padding(.bottom, 32) - } - } - } - } - - // MARK: - Scan Lines Overlay - - private var scanLinesOverlay: some View { - GeometryReader { geo in - VStack(spacing: 2) { - ForEach(0.. some View { - HStack(spacing: 12) { - // Sport icon with glow - ZStack { - Circle() - .fill(trip.uniqueSports.first?.themeColor.opacity(0.2) ?? neonCyan.opacity(0.2)) - .frame(width: 44, height: 44) - - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt") - .font(.system(size: 18)) - .foregroundStyle(trip.uniqueSports.first?.themeColor ?? neonCyan) - } - .shadow(color: trip.uniqueSports.first?.themeColor.opacity(0.5) ?? neonCyan.opacity(0.5), radius: 10) - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName.uppercased()) - .font(.system(size: 14, weight: .bold, design: .rounded)) - .foregroundStyle(chrome) - - HStack(spacing: 8) { - Text("\(trip.stops.count) CITIES") - .font(.system(size: 10, design: .monospaced)) - .foregroundStyle(neonCyan) - - Text("•") - .foregroundStyle(chrome.opacity(0.3)) - - Text("\(trip.totalGames) GAMES") - .font(.system(size: 10, design: .monospaced)) - .foregroundStyle(neonMagenta) - } - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(neonCyan) - } - .padding(16) - .background( - RoundedRectangle(cornerRadius: 8) - .stroke( - LinearGradient( - colors: [neonCyan.opacity(0.5), neonMagenta.opacity(0.5)], - startPoint: .leading, - endPoint: .trailing - ), - lineWidth: 1 - ) - .background(Color.white.opacity(0.02)) - ) - } - - // MARK: - Saved Section - - private var savedSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Rectangle() - .fill(neonYellow) - .frame(width: 4, height: 20) - - Text("YOUR TRIPS") - .font(.system(size: 14, weight: .bold, design: .rounded)) - .foregroundStyle(chrome) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("VIEW ALL ▸") - .font(.system(size: 10, weight: .bold, design: .monospaced)) - .foregroundStyle(neonYellow) - } - } - - ForEach(savedTrips.prefix(3)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - HStack { - Text(trip.displayName) - .font(.system(size: 13, weight: .medium, design: .rounded)) - .foregroundStyle(chrome.opacity(0.8)) - - Spacer() - - Text("\(trip.stops.count)") - .font(.system(size: 12, design: .monospaced)) - .foregroundStyle(neonYellow) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(neonYellow.opacity(0.1)) - .clipShape(RoundedRectangle(cornerRadius: 4)) - } - .padding(.vertical, 12) - .overlay(alignment: .bottom) { - Rectangle() - .fill(chrome.opacity(0.1)) - .frame(height: 1) - } - } - .buttonStyle(.plain) - } - } - } - } - - // MARK: - Retro Footer - - private var retroFooter: some View { - VStack(spacing: 8) { - Rectangle() - .fill( - LinearGradient( - colors: [neonMagenta.opacity(0), neonMagenta, neonMagenta.opacity(0)], - startPoint: .leading, - endPoint: .trailing - ) - ) - .frame(height: 1) - .padding(.horizontal, 60) - - Text("◀ SPORTS TIME SYSTEMS ▶") - .font(.system(size: 9, weight: .bold, design: .monospaced)) - .foregroundStyle(chrome.opacity(0.3)) - .tracking(2) - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/SeatGeek/HomeContent_SeatGeek.swift b/SportsTime/Features/Home/Views/Variants/SeatGeek/HomeContent_SeatGeek.swift deleted file mode 100644 index a2d4061..0000000 --- a/SportsTime/Features/Home/Views/Variants/SeatGeek/HomeContent_SeatGeek.swift +++ /dev/null @@ -1,370 +0,0 @@ -// -// HomeContent_SeatGeek.swift -// SportsTime -// -// SEATGEEK-INSPIRED: Sports ticketing aesthetic. -// Modern cards, vibrant accents, event-focused design. -// Professional yet energetic feel. -// - -import SwiftUI -import SwiftData - -struct HomeContent_SeatGeek: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // SeatGeek-inspired colors - private var bgColor: Color { - colorScheme == .dark - ? Color(red: 0.08, green: 0.08, blue: 0.10) - : Color(red: 0.96, green: 0.96, blue: 0.97) - } - - private var cardBg: Color { - colorScheme == .dark - ? Color(red: 0.14, green: 0.14, blue: 0.16) - : Color.white - } - - private let accentMagenta = Color(red: 0.85, green: 0.15, blue: 0.5) - private let accentTeal = Color(red: 0.0, green: 0.75, blue: 0.7) - - private var textPrimary: Color { - colorScheme == .dark ? .white : Color(red: 0.12, green: 0.12, blue: 0.15) - } - - private var textSecondary: Color { - colorScheme == .dark ? Color(white: 0.5) : Color(white: 0.4) - } - - var body: some View { - ScrollView { - VStack(spacing: 20) { - // Hero banner - heroBanner - .padding(.horizontal, 16) - .padding(.top, 8) - - // Your trips section - if !savedTrips.isEmpty { - yourTripsSection - } - - // Trending trips - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - trendingSection - } - - // Browse by sport - browseBySportSection - .padding(.horizontal, 16) - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 16) - .padding(.bottom, 32) - } - } - } - .background(bgColor.ignoresSafeArea()) - } - - // MARK: - Hero Banner - - private var heroBanner: some View { - Button { - showNewTrip = true - } label: { - ZStack(alignment: .bottomLeading) { - // Gradient background - RoundedRectangle(cornerRadius: 16) - .fill( - LinearGradient( - colors: [ - accentMagenta, - Color(red: 0.55, green: 0.1, blue: 0.6) - ], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(height: 160) - - // Pattern overlay - GeometryReader { geo in - Path { path in - let w = geo.size.width - let h = geo.size.height - for i in stride(from: 0, to: w, by: 30) { - path.move(to: CGPoint(x: i, y: h)) - path.addLine(to: CGPoint(x: i + 60, y: 0)) - } - } - .stroke(Color.white.opacity(0.1), lineWidth: 1) - } - - // Content - VStack(alignment: .leading, spacing: 8) { - Text("Plan Your Trip") - .font(.system(size: 24, weight: .bold)) - .foregroundStyle(.white) - - Text("Find games along any route") - .font(.system(size: 14)) - .foregroundStyle(.white.opacity(0.85)) - - HStack(spacing: 6) { - Text("Get Started") - .font(.system(size: 13, weight: .semibold)) - Image(systemName: "arrow.right") - .font(.system(size: 11, weight: .bold)) - } - .foregroundStyle(.white) - .padding(.horizontal, 14) - .padding(.vertical, 8) - .background( - Capsule() - .fill(.white.opacity(0.2)) - ) - .padding(.top, 4) - } - .padding(20) - } - } - .buttonStyle(.plain) - } - - // MARK: - Your Trips Section - - private var yourTripsSection: some View { - VStack(alignment: .leading, spacing: 14) { - HStack { - Text("Your Trips") - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("View All") - .font(.system(size: 14, weight: .semibold)) - .foregroundStyle(accentMagenta) - } - } - .padding(.horizontal, 16) - - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 12) { - ForEach(savedTrips.prefix(4)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - yourTripCard(trip) - } - .buttonStyle(.plain) - } - } - } - .padding(.horizontal, 16) - } - } - } - - private func yourTripCard(_ trip: Trip) -> some View { - VStack(alignment: .leading, spacing: 10) { - // Sport badge - HStack { - if let sport = trip.uniqueSports.first { - HStack(spacing: 4) { - Image(systemName: sport.iconName) - .font(.system(size: 10)) - Text(sport.displayName) - .font(.system(size: 10, weight: .semibold)) - } - .foregroundStyle(sport.themeColor) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background( - Capsule() - .fill(sport.themeColor.opacity(0.15)) - ) - } - Spacer() - } - - Text(trip.displayName) - .font(.system(size: 15, weight: .semibold)) - .foregroundStyle(textPrimary) - .lineLimit(2) - .multilineTextAlignment(.leading) - - Spacer() - - HStack(spacing: 12) { - Label("\(trip.stops.count)", systemImage: "mappin") - Label("\(trip.totalGames)", systemImage: "ticket.fill") - } - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - - Text(trip.formattedDateRange) - .font(.system(size: 11)) - .foregroundStyle(textSecondary) - } - .padding(14) - .frame(width: 160, height: 150) - .background( - RoundedRectangle(cornerRadius: 14) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.3 : 0.08), radius: 8, y: 3) - ) - } - - // MARK: - Trending Section - - private var trendingSection: some View { - VStack(alignment: .leading, spacing: 14) { - HStack { - HStack(spacing: 6) { - Image(systemName: "flame.fill") - .foregroundStyle(.orange) - Text("Trending Routes") - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(textPrimary) - } - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(textSecondary) - } - } - .padding(.horizontal, 16) - - VStack(spacing: 10) { - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(4)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - trendingCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - .padding(.horizontal, 16) - } - } - - private func trendingCard(_ trip: Trip) -> some View { - HStack(spacing: 14) { - // Event icon - ZStack { - RoundedRectangle(cornerRadius: 10) - .fill( - LinearGradient( - colors: [accentTeal, accentTeal.opacity(0.7)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 52, height: 52) - - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt.fill") - .font(.system(size: 22)) - .foregroundStyle(.white) - } - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 15, weight: .semibold)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Text("\(trip.stops.count) cities • \(trip.totalGames) games") - .font(.system(size: 13)) - .foregroundStyle(textSecondary) - } - - Spacer() - - // Deal badge - Text("HOT") - .font(.system(size: 10, weight: .bold)) - .foregroundStyle(.white) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background( - Capsule() - .fill(.orange) - ) - } - .padding(12) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.25 : 0.06), radius: 6, y: 2) - ) - } - - // MARK: - Browse by Sport - - private var browseBySportSection: some View { - VStack(alignment: .leading, spacing: 14) { - Text("Browse by Sport") - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(textPrimary) - - LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 12) { - ForEach(Sport.supported.prefix(4)) { sport in - sportCard(sport) - } - } - } - } - - private func sportCard(_ sport: Sport) -> some View { - Button { - showNewTrip = true - } label: { - HStack(spacing: 10) { - Image(systemName: sport.iconName) - .font(.system(size: 18)) - .foregroundStyle(sport.themeColor) - - Text(sport.displayName) - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(textPrimary) - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 11, weight: .medium)) - .foregroundStyle(textSecondary.opacity(0.5)) - } - .padding(14) - .background( - RoundedRectangle(cornerRadius: 10) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.2 : 0.04), radius: 4, y: 1) - ) - } - .buttonStyle(.plain) - } -} diff --git a/SportsTime/Features/Home/Views/Variants/SoftPastel/HomeContent_SoftPastel.swift b/SportsTime/Features/Home/Views/Variants/SoftPastel/HomeContent_SoftPastel.swift deleted file mode 100644 index 9bb37f8..0000000 --- a/SportsTime/Features/Home/Views/Variants/SoftPastel/HomeContent_SoftPastel.swift +++ /dev/null @@ -1,397 +0,0 @@ -// -// HomeContent_SoftPastel.swift -// SportsTime -// -// SOFT PASTEL: Gentle, dreamy, calming. -// Muted colors, soft shadows, rounded everything. -// Cozy travel journal aesthetic. -// - -import SwiftUI -import SwiftData - -struct HomeContent_SoftPastel: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Soft pastel palette - private let pastelPink = Color(red: 1.0, green: 0.85, blue: 0.88) - private let pastelBlue = Color(red: 0.85, green: 0.92, blue: 1.0) - private let pastelMint = Color(red: 0.85, green: 0.98, blue: 0.92) - private let pastelLavender = Color(red: 0.92, green: 0.88, blue: 1.0) - private let pastelPeach = Color(red: 1.0, green: 0.9, blue: 0.85) - - private var bgColor: Color { - colorScheme == .dark - ? Color(red: 0.12, green: 0.12, blue: 0.14) - : Color(red: 0.98, green: 0.97, blue: 0.99) - } - - private var textPrimary: Color { - colorScheme == .dark ? Color(white: 0.9) : Color(red: 0.3, green: 0.28, blue: 0.35) - } - - private var textSecondary: Color { - colorScheme == .dark ? Color(white: 0.6) : Color(red: 0.5, green: 0.48, blue: 0.55) - } - - var body: some View { - ZStack { - bgColor.ignoresSafeArea() - - // Soft gradient clouds - softClouds - - ScrollView { - VStack(spacing: 28) { - // PASTEL HERO - pastelHero - .padding(.top, 20) - .padding(.horizontal, 20) - - // FEATURED TRIPS - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - featuredSection - .padding(.horizontal, 20) - } - - // SAVED TRIPS - if !savedTrips.isEmpty { - savedSection - .padding(.horizontal, 20) - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 20) - } - - // SOFT FOOTER - softFooter - .padding(.bottom, 32) - } - } - } - } - - // MARK: - Soft Clouds - - private var softClouds: some View { - ZStack { - // Soft gradient blobs - Ellipse() - .fill( - LinearGradient( - colors: [pastelPink.opacity(0.4), pastelPink.opacity(0.1)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 300, height: 200) - .blur(radius: 50) - .offset(x: -80, y: -150) - - Ellipse() - .fill( - LinearGradient( - colors: [pastelBlue.opacity(0.4), pastelBlue.opacity(0.1)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 250, height: 180) - .blur(radius: 45) - .offset(x: 100, y: 150) - - Ellipse() - .fill( - LinearGradient( - colors: [pastelMint.opacity(0.3), pastelMint.opacity(0.1)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 220, height: 160) - .blur(radius: 40) - .offset(x: -50, y: 450) - - Ellipse() - .fill( - LinearGradient( - colors: [pastelLavender.opacity(0.3), pastelLavender.opacity(0.1)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 200, height: 150) - .blur(radius: 35) - .offset(x: 80, y: 600) - } - .allowsHitTesting(false) - } - - // MARK: - Pastel Hero - - private var pastelHero: some View { - VStack(spacing: 24) { - // Soft icon cluster - ZStack { - Circle() - .fill(pastelPink.opacity(colorScheme == .dark ? 0.3 : 0.6)) - .frame(width: 80, height: 80) - .offset(x: -30, y: -10) - - Circle() - .fill(pastelBlue.opacity(colorScheme == .dark ? 0.3 : 0.6)) - .frame(width: 70, height: 70) - .offset(x: 25, y: 5) - - Circle() - .fill(pastelMint.opacity(colorScheme == .dark ? 0.3 : 0.6)) - .frame(width: 60, height: 60) - .offset(x: -5, y: 25) - - Image(systemName: "car.fill") - .font(.system(size: 28)) - .foregroundStyle(textPrimary.opacity(0.7)) - } - .padding(.vertical, 8) - - // Title - VStack(spacing: 8) { - Text("Sports Time") - .font(.system(size: 32, weight: .semibold, design: .rounded)) - .foregroundStyle(textPrimary) - - Text("Plan your cozy road trip adventure") - .font(.system(size: 15, weight: .regular, design: .rounded)) - .foregroundStyle(textSecondary) - } - - // Soft CTA - Button { - showNewTrip = true - } label: { - HStack(spacing: 12) { - Text("Start Planning") - .font(.system(size: 16, weight: .semibold, design: .rounded)) - - Image(systemName: "arrow.right") - .font(.system(size: 14, weight: .medium)) - } - .foregroundStyle(textPrimary) - .padding(.horizontal, 32) - .padding(.vertical, 16) - .background( - Capsule() - .fill( - LinearGradient( - colors: colorScheme == .dark - ? [pastelPink.opacity(0.4), pastelLavender.opacity(0.4)] - : [pastelPink, pastelLavender], - startPoint: .leading, - endPoint: .trailing - ) - ) - ) - .shadow(color: pastelPink.opacity(0.3), radius: 15, y: 6) - } - } - .padding(28) - .background( - RoundedRectangle(cornerRadius: 32) - .fill(colorScheme == .dark ? Color.white.opacity(0.06) : Color.white.opacity(0.8)) - .shadow(color: Color.black.opacity(0.05), radius: 20, y: 8) - ) - } - - // MARK: - Featured Section - - private var featuredSection: some View { - VStack(alignment: .leading, spacing: 18) { - HStack { - Text("Featured Trips") - .font(.system(size: 20, weight: .semibold, design: .rounded)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 14)) - .foregroundStyle(textSecondary) - .padding(10) - .background( - Circle() - .fill(colorScheme == .dark ? Color.white.opacity(0.06) : Color.white.opacity(0.8)) - ) - } - } - - ForEach(Array(suggestedTripsGenerator.suggestedTrips.prefix(4).enumerated()), id: \.element.id) { index, suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - pastelTripCard(suggestedTrip.trip, colorIndex: index) - } - .buttonStyle(.plain) - } - } - } - - private func pastelTripCard(_ trip: Trip, colorIndex: Int) -> some View { - let colors = [pastelPink, pastelBlue, pastelMint, pastelLavender, pastelPeach] - let accentColor = colors[colorIndex % colors.count] - - return HStack(spacing: 16) { - // Soft circle - Circle() - .fill(accentColor.opacity(colorScheme == .dark ? 0.3 : 0.6)) - .frame(width: 50, height: 50) - .overlay( - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt.fill") - .font(.system(size: 20)) - .foregroundStyle(textPrimary.opacity(0.7)) - ) - - VStack(alignment: .leading, spacing: 6) { - Text(trip.displayName) - .font(.system(size: 16, weight: .medium, design: .rounded)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - HStack(spacing: 12) { - HStack(spacing: 4) { - Image(systemName: "mappin.circle.fill") - .font(.system(size: 12)) - Text("\(trip.stops.count) stops") - .font(.system(size: 13, design: .rounded)) - } - .foregroundStyle(textSecondary) - - HStack(spacing: 4) { - Image(systemName: "sportscourt.fill") - .font(.system(size: 12)) - Text("\(trip.totalGames) games") - .font(.system(size: 13, design: .rounded)) - } - .foregroundStyle(textSecondary) - } - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 12, weight: .medium)) - .foregroundStyle(textSecondary.opacity(0.6)) - } - .padding(16) - .background( - RoundedRectangle(cornerRadius: 20) - .fill(colorScheme == .dark ? Color.white.opacity(0.05) : Color.white.opacity(0.8)) - .shadow(color: accentColor.opacity(0.2), radius: 10, y: 4) - ) - } - - // MARK: - Saved Section - - private var savedSection: some View { - VStack(alignment: .leading, spacing: 18) { - HStack { - Text("Your Trips") - .font(.system(size: 20, weight: .semibold, design: .rounded)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("See all") - .font(.system(size: 14, weight: .medium, design: .rounded)) - .foregroundStyle(textSecondary) - } - } - - ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - let colors = [pastelPeach, pastelMint, pastelLavender] - let accentColor = colors[index % colors.count] - - HStack(spacing: 14) { - // Soft dot - Circle() - .fill(accentColor.opacity(colorScheme == .dark ? 0.4 : 0.7)) - .frame(width: 12, height: 12) - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 15, weight: .medium, design: .rounded)) - .foregroundStyle(textPrimary) - - Text(trip.formattedDateRange) - .font(.system(size: 12, design: .rounded)) - .foregroundStyle(textSecondary) - } - - Spacer() - - Text("\(trip.stops.count)") - .font(.system(size: 13, weight: .medium, design: .rounded)) - .foregroundStyle(textPrimary.opacity(0.7)) - .padding(.horizontal, 12) - .padding(.vertical, 6) - .background( - Capsule() - .fill(accentColor.opacity(colorScheme == .dark ? 0.2 : 0.4)) - ) - } - .padding(14) - .background( - RoundedRectangle(cornerRadius: 16) - .fill(colorScheme == .dark ? Color.white.opacity(0.04) : Color.white.opacity(0.7)) - ) - } - .buttonStyle(.plain) - } - } - } - } - - // MARK: - Soft Footer - - private var softFooter: some View { - VStack(spacing: 12) { - // Soft dots - HStack(spacing: 8) { - Circle() - .fill(pastelPink.opacity(0.5)) - .frame(width: 6, height: 6) - Circle() - .fill(pastelBlue.opacity(0.5)) - .frame(width: 6, height: 6) - Circle() - .fill(pastelMint.opacity(0.5)) - .frame(width: 6, height: 6) - } - - Text("Sports Time") - .font(.system(size: 11, weight: .medium, design: .rounded)) - .foregroundStyle(textSecondary.opacity(0.5)) - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/Spotify/HomeContent_Spotify.swift b/SportsTime/Features/Home/Views/Variants/Spotify/HomeContent_Spotify.swift deleted file mode 100644 index 6faa95b..0000000 --- a/SportsTime/Features/Home/Views/Variants/Spotify/HomeContent_Spotify.swift +++ /dev/null @@ -1,276 +0,0 @@ -// -// HomeContent_Spotify.swift -// SportsTime -// -// SPOTIFY-INSPIRED: Dark elegance, bold typography. -// Content-focused cards, horizontal scrolling. -// Green accent, immersive feel. -// - -import SwiftUI -import SwiftData - -struct HomeContent_Spotify: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Spotify-inspired colors (dark-first design) - private let bgColor = Color(red: 0.07, green: 0.07, blue: 0.07) - private let cardBg = Color(red: 0.11, green: 0.11, blue: 0.11) - private let spotifyGreen = Color(red: 0.12, green: 0.84, blue: 0.38) - - private let textPrimary = Color.white - private let textSecondary = Color(white: 0.65) - - var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 28) { - // Greeting header - greetingHeader - .padding(.horizontal, 16) - .padding(.top, 12) - - // Your trips - if !savedTrips.isEmpty { - yourTripsSection - } - - // Made for you - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - madeForYouSection - } - - // Browse sports - browseSportsSection - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 16) - } - - Spacer(minLength: 50) - } - } - .background(bgColor.ignoresSafeArea()) - } - - // MARK: - Greeting Header - - private var greetingHeader: some View { - HStack { - Text(greetingText) - .font(.system(size: 24, weight: .bold)) - .foregroundStyle(textPrimary) - - Spacer() - - // Settings/gear button - Button { - // Navigate to settings - } label: { - Image(systemName: "gearshape.fill") - .font(.system(size: 18)) - .foregroundStyle(textPrimary) - } - } - } - - private var greetingText: String { - let hour = Calendar.current.component(.hour, from: Date()) - if hour < 12 { - return "Good morning" - } else if hour < 17 { - return "Good afternoon" - } else { - return "Good evening" - } - } - - // MARK: - Your Trips Section - - private var yourTripsSection: some View { - VStack(alignment: .leading, spacing: 16) { - Text("Your Trips") - .font(.system(size: 22, weight: .bold)) - .foregroundStyle(textPrimary) - .padding(.horizontal, 16) - - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 16) { - ForEach(savedTrips.prefix(5)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - tripCoverCard(trip) - } - .buttonStyle(.plain) - } - } - } - .padding(.horizontal, 16) - } - } - } - - private func tripCoverCard(_ trip: Trip) -> some View { - VStack(alignment: .leading, spacing: 10) { - // Album art style cover - ZStack { - RoundedRectangle(cornerRadius: 6) - .fill( - LinearGradient( - colors: [ - trip.uniqueSports.first?.themeColor ?? spotifyGreen, - (trip.uniqueSports.first?.themeColor ?? spotifyGreen).opacity(0.4) - ], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 150, height: 150) - - VStack(spacing: 8) { - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt.fill") - .font(.system(size: 40)) - .foregroundStyle(.white) - - Text("\(trip.totalGames) games") - .font(.system(size: 12, weight: .semibold)) - .foregroundStyle(.white.opacity(0.9)) - } - } - .shadow(color: Color.black.opacity(0.4), radius: 8, y: 4) - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 14, weight: .semibold)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Text("\(trip.stops.count) stops") - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - } - } - .frame(width: 150) - } - - // MARK: - Made For You Section - - private var madeForYouSection: some View { - VStack(alignment: .leading, spacing: 16) { - Text("Made For You") - .font(.system(size: 22, weight: .bold)) - .foregroundStyle(textPrimary) - .padding(.horizontal, 16) - - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 16) { - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(5)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - suggestionCoverCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - .padding(.horizontal, 16) - } - } - } - - private func suggestionCoverCard(_ trip: Trip) -> some View { - VStack(alignment: .leading, spacing: 10) { - // Cover with gradient mesh - ZStack { - RoundedRectangle(cornerRadius: 6) - .fill( - LinearGradient( - colors: [ - Color(red: 0.3, green: 0.2, blue: 0.5), - Color(red: 0.1, green: 0.4, blue: 0.5), - Color(red: 0.2, green: 0.3, blue: 0.4) - ], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 150, height: 150) - - VStack(spacing: 6) { - Image(systemName: "sparkles") - .font(.system(size: 24)) - .foregroundStyle(.white.opacity(0.8)) - - Text("Daily Mix") - .font(.system(size: 11, weight: .bold)) - .foregroundStyle(.white.opacity(0.8)) - } - } - .shadow(color: Color.black.opacity(0.4), radius: 8, y: 4) - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 14, weight: .semibold)) - .foregroundStyle(textPrimary) - .lineLimit(2) - - Text("\(trip.stops.count) cities") - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - } - } - .frame(width: 150) - } - - // MARK: - Browse Sports Section - - private var browseSportsSection: some View { - VStack(alignment: .leading, spacing: 16) { - Text("Browse Sports") - .font(.system(size: 22, weight: .bold)) - .foregroundStyle(textPrimary) - .padding(.horizontal, 16) - - LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 12) { - ForEach(Sport.supported.prefix(4)) { sport in - sportBrowseCard(sport) - } - } - .padding(.horizontal, 16) - } - } - - private func sportBrowseCard(_ sport: Sport) -> some View { - Button { - showNewTrip = true - } label: { - ZStack(alignment: .bottomLeading) { - RoundedRectangle(cornerRadius: 6) - .fill( - LinearGradient( - colors: [sport.themeColor, sport.themeColor.opacity(0.6)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(height: 100) - - Text(sport.displayName) - .font(.system(size: 16, weight: .bold)) - .foregroundStyle(.white) - .padding(12) - } - } - .buttonStyle(.plain) - } -} diff --git a/SportsTime/Features/Home/Views/Variants/Strava/HomeContent_Strava.swift b/SportsTime/Features/Home/Views/Variants/Strava/HomeContent_Strava.swift deleted file mode 100644 index 60593c7..0000000 --- a/SportsTime/Features/Home/Views/Variants/Strava/HomeContent_Strava.swift +++ /dev/null @@ -1,378 +0,0 @@ -// -// HomeContent_Strava.swift -// SportsTime -// -// STRAVA-INSPIRED: Athletic, data-driven. -// Orange accent, activity stats, route-focused. -// Performance metrics and community feel. -// - -import SwiftUI -import SwiftData - -struct HomeContent_Strava: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Strava-inspired colors - private var bgColor: Color { - colorScheme == .dark - ? Color(red: 0.08, green: 0.08, blue: 0.1) - : Color(red: 0.96, green: 0.96, blue: 0.97) - } - - private var cardBg: Color { - colorScheme == .dark - ? Color(red: 0.12, green: 0.12, blue: 0.14) - : Color.white - } - - private let stravaOrange = Color(red: 0.99, green: 0.32, blue: 0.15) - - private var textPrimary: Color { - colorScheme == .dark ? .white : Color(red: 0.12, green: 0.12, blue: 0.14) - } - - private var textSecondary: Color { - colorScheme == .dark ? Color(white: 0.55) : Color(white: 0.45) - } - - var body: some View { - ScrollView { - VStack(spacing: 20) { - // Profile header - profileHeader - .padding(.horizontal, 16) - .padding(.top, 8) - - // Stats overview - statsOverview - .padding(.horizontal, 16) - - // Record button - recordButton - .padding(.horizontal, 16) - - // Recent activities - if !savedTrips.isEmpty { - recentActivities - } - - // Routes - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - routesSection - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 16) - } - - Spacer(minLength: 40) - } - } - .background(bgColor.ignoresSafeArea()) - } - - // MARK: - Profile Header - - private var profileHeader: some View { - HStack(spacing: 14) { - // Profile avatar - Circle() - .fill( - LinearGradient( - colors: [stravaOrange, stravaOrange.opacity(0.7)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 50, height: 50) - .overlay( - Image(systemName: "person.fill") - .font(.system(size: 22)) - .foregroundStyle(.white) - ) - - VStack(alignment: .leading, spacing: 2) { - Text("Sports Time") - .font(.system(size: 18, weight: .bold)) - .foregroundStyle(textPrimary) - - Text("Trip Planner") - .font(.system(size: 13)) - .foregroundStyle(textSecondary) - } - - Spacer() - - // Notifications - Button {} label: { - Image(systemName: "bell.fill") - .font(.system(size: 18)) - .foregroundStyle(textSecondary) - } - } - } - - // MARK: - Stats Overview - - private var statsOverview: some View { - HStack(spacing: 0) { - statBlock(value: "\(savedTrips.count)", label: "Trips", color: stravaOrange) - statDivider - statBlock(value: "\(totalGames)", label: "Games", color: .blue) - statDivider - statBlock(value: "\(totalStops)", label: "Cities", color: .green) - } - .padding(.vertical, 16) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.25 : 0.06), radius: 6, y: 2) - ) - } - - private func statBlock(value: String, label: String, color: Color) -> some View { - VStack(spacing: 4) { - Text(value) - .font(.system(size: 24, weight: .bold)) - .foregroundStyle(textPrimary) - - Text(label) - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - } - .frame(maxWidth: .infinity) - } - - private var statDivider: some View { - Rectangle() - .fill(textSecondary.opacity(0.2)) - .frame(width: 1, height: 40) - } - - private var totalGames: Int { - savedTrips.compactMap { $0.trip?.totalGames }.reduce(0, +) - } - - private var totalStops: Int { - savedTrips.compactMap { $0.trip?.stops.count }.reduce(0, +) - } - - // MARK: - Record Button - - private var recordButton: some View { - Button { - showNewTrip = true - } label: { - HStack(spacing: 10) { - Image(systemName: "plus.circle.fill") - .font(.system(size: 20)) - - Text("Plan New Trip") - .font(.system(size: 16, weight: .semibold)) - } - .foregroundStyle(.white) - .frame(maxWidth: .infinity) - .padding(.vertical, 14) - .background( - RoundedRectangle(cornerRadius: 10) - .fill(stravaOrange) - ) - } - .buttonStyle(.plain) - } - - // MARK: - Recent Activities - - private var recentActivities: some View { - VStack(alignment: .leading, spacing: 14) { - HStack { - Text("Your Activities") - .font(.system(size: 18, weight: .bold)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("View All") - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(stravaOrange) - } - } - .padding(.horizontal, 16) - - VStack(spacing: 12) { - ForEach(savedTrips.prefix(3)) { savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - activityCard(trip) - } - .buttonStyle(.plain) - } - } - } - .padding(.horizontal, 16) - } - } - - private func activityCard(_ trip: Trip) -> some View { - VStack(spacing: 12) { - // Header - HStack(spacing: 10) { - Circle() - .fill(trip.uniqueSports.first?.themeColor ?? stravaOrange) - .frame(width: 36, height: 36) - .overlay( - Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt.fill") - .font(.system(size: 14)) - .foregroundStyle(.white) - ) - - VStack(alignment: .leading, spacing: 2) { - Text(trip.displayName) - .font(.system(size: 15, weight: .semibold)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Text(trip.formattedDateRange) - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 12)) - .foregroundStyle(textSecondary) - } - - // Stats row - HStack(spacing: 24) { - activityStat(value: "\(trip.stops.count)", label: "Cities") - activityStat(value: "\(trip.totalGames)", label: "Games") - if let sport = trip.uniqueSports.first { - activityStat(value: sport.displayName, label: "Sport") - } - } - } - .padding(14) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.2 : 0.05), radius: 4, y: 2) - ) - } - - private func activityStat(value: String, label: String) -> some View { - VStack(alignment: .leading, spacing: 2) { - Text(value) - .font(.system(size: 14, weight: .semibold)) - .foregroundStyle(textPrimary) - - Text(label) - .font(.system(size: 11)) - .foregroundStyle(textSecondary) - } - } - - // MARK: - Routes Section - - private var routesSection: some View { - VStack(alignment: .leading, spacing: 14) { - HStack { - Text("Suggested Routes") - .font(.system(size: 18, weight: .bold)) - .foregroundStyle(textPrimary) - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 14)) - .foregroundStyle(stravaOrange) - } - } - .padding(.horizontal, 16) - - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 12) { - ForEach(suggestedTripsGenerator.suggestedTrips.prefix(4)) { suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - routeCard(suggestedTrip.trip) - } - .buttonStyle(.plain) - } - } - .padding(.horizontal, 16) - } - } - } - - private func routeCard(_ trip: Trip) -> some View { - VStack(alignment: .leading, spacing: 10) { - // Route preview - ZStack { - RoundedRectangle(cornerRadius: 10) - .fill( - LinearGradient( - colors: [ - trip.uniqueSports.first?.themeColor.opacity(0.3) ?? stravaOrange.opacity(0.3), - trip.uniqueSports.first?.themeColor.opacity(0.1) ?? stravaOrange.opacity(0.1) - ], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(height: 80) - - // Simplified route line - Path { path in - path.move(to: CGPoint(x: 20, y: 60)) - path.addCurve( - to: CGPoint(x: 120, y: 20), - control1: CGPoint(x: 50, y: 40), - control2: CGPoint(x: 90, y: 30) - ) - } - .stroke(stravaOrange, lineWidth: 3) - } - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 13, weight: .semibold)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Text("\(trip.stops.count) cities • \(trip.totalGames) games") - .font(.system(size: 11)) - .foregroundStyle(textSecondary) - } - } - .frame(width: 140) - .padding(10) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(cardBg) - .shadow(color: Color.black.opacity(colorScheme == .dark ? 0.2 : 0.05), radius: 4, y: 2) - ) - } -} diff --git a/SportsTime/Features/Home/Views/Variants/SwissModernist/HomeContent_SwissModernist.swift b/SportsTime/Features/Home/Views/Variants/SwissModernist/HomeContent_SwissModernist.swift deleted file mode 100644 index 90e2213..0000000 --- a/SportsTime/Features/Home/Views/Variants/SwissModernist/HomeContent_SwissModernist.swift +++ /dev/null @@ -1,367 +0,0 @@ -// -// HomeContent_SwissModernist.swift -// SportsTime -// -// SWISS MODERNIST: Grid perfection, Helvetica vibes, mathematical precision. -// Clean typography, strict alignment, minimalist color with bold accents. -// - -import SwiftUI -import SwiftData - -struct HomeContent_SwissModernist: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Swiss design palette - restrained with bold accent - private let swissRed = Color(red: 1.0, green: 0.0, blue: 0.0) - private let swissBlack = Color(white: 0.0) - - private var bgColor: Color { - colorScheme == .dark ? Color(white: 0.06) : Color(white: 0.98) - } - - private var textPrimary: Color { - colorScheme == .dark ? .white : swissBlack - } - - private var textSecondary: Color { - colorScheme == .dark ? Color(white: 0.5) : Color(white: 0.4) - } - - private var gridLine: Color { - colorScheme == .dark ? Color(white: 0.15) : Color(white: 0.85) - } - - var body: some View { - ZStack { - bgColor.ignoresSafeArea() - - // Subtle grid overlay - gridOverlay - - ScrollView { - VStack(spacing: 0) { - // HEADER - Strict typographic hierarchy - swissHeader - .padding(.top, 32) - .padding(.horizontal, 24) - - // HERO - Mathematical precision - swissHero - .padding(.top, 48) - .padding(.horizontal, 24) - - // FEATURED TRIPS - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - featuredSection - .padding(.top, 64) - .padding(.horizontal, 24) - } - - // SAVED TRIPS - if !savedTrips.isEmpty { - savedSection - .padding(.top, 64) - .padding(.horizontal, 24) - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.top, 64) - .padding(.horizontal, 24) - } - - // FOOTER - swissFooter - .padding(.top, 80) - .padding(.bottom, 40) - } - } - } - } - - // MARK: - Grid Overlay - - private var gridOverlay: some View { - GeometryReader { geo in - // Vertical grid lines - HStack(spacing: geo.size.width / 12) { - ForEach(0..<12, id: \.self) { _ in - Rectangle() - .fill(gridLine.opacity(0.3)) - .frame(width: 0.5) - } - } - .padding(.horizontal, 24) - } - .allowsHitTesting(false) - } - - // MARK: - Swiss Header - - private var swissHeader: some View { - VStack(alignment: .leading, spacing: 12) { - // Date - small caps style - Text(Date.now.formatted(.dateTime.month(.wide).year()).uppercased()) - .font(.system(size: 10, weight: .medium)) - .tracking(3) - .foregroundStyle(textSecondary) - - // Rule - Rectangle() - .fill(textPrimary) - .frame(height: 2) - .frame(maxWidth: 60) - } - } - - // MARK: - Swiss Hero - - private var swissHero: some View { - VStack(alignment: .leading, spacing: 32) { - // Title block - strict typographic scale - VStack(alignment: .leading, spacing: 8) { - Text("Sports") - .font(.system(size: 56, weight: .bold)) - .foregroundStyle(textPrimary) - - HStack(spacing: 0) { - Text("Time") - .font(.system(size: 56, weight: .bold)) - .foregroundStyle(textPrimary) - - // Red accent dot - Circle() - .fill(swissRed) - .frame(width: 14, height: 14) - .offset(y: 16) - } - } - .tracking(-1) - - // Description - rational grid width - Text("Plan your perfect sports road trip with mathematical precision. Every route optimized. Every game aligned.") - .font(.system(size: 16, weight: .regular)) - .foregroundStyle(textSecondary) - .lineSpacing(6) - .frame(maxWidth: 280, alignment: .leading) - - // CTA - Swiss button - Button { - showNewTrip = true - } label: { - HStack(spacing: 16) { - Text("Begin") - .font(.system(size: 14, weight: .semibold)) - - Rectangle() - .fill(Color.white) - .frame(width: 24, height: 1) - - Image(systemName: "arrow.right") - .font(.system(size: 12, weight: .medium)) - } - .foregroundStyle(.white) - .padding(.horizontal, 32) - .padding(.vertical, 18) - .background(swissRed) - } - } - } - - // MARK: - Featured Section - - private var featuredSection: some View { - VStack(alignment: .leading, spacing: 32) { - // Section header - typographic hierarchy - HStack(alignment: .top) { - VStack(alignment: .leading, spacing: 8) { - Text("01") - .font(.system(size: 11, weight: .medium, design: .monospaced)) - .foregroundStyle(swissRed) - - Text("Featured") - .font(.system(size: 28, weight: .bold)) - .foregroundStyle(textPrimary) - } - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 14)) - .foregroundStyle(textSecondary) - } - } - - // Rule - Rectangle() - .fill(textPrimary) - .frame(height: 1) - - // Grid of trips - VStack(spacing: 0) { - ForEach(Array(suggestedTripsGenerator.suggestedTrips.prefix(4).enumerated()), id: \.element.id) { index, suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - swissTripRow(suggestedTrip.trip, index: index + 1) - } - .buttonStyle(.plain) - } - } - } - } - - private func swissTripRow(_ trip: Trip, index: Int) -> some View { - VStack(spacing: 0) { - HStack(alignment: .center, spacing: 24) { - // Index number - Text(String(format: "%02d", index)) - .font(.system(size: 12, weight: .medium, design: .monospaced)) - .foregroundStyle(textSecondary) - .frame(width: 24) - - // Trip name - Text(trip.displayName) - .font(.system(size: 15, weight: .medium)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Spacer() - - // Stats - tabular - HStack(spacing: 24) { - VStack(alignment: .trailing, spacing: 2) { - Text("\(trip.stops.count)") - .font(.system(size: 14, weight: .semibold, design: .monospaced)) - .foregroundStyle(textPrimary) - Text("cities") - .font(.system(size: 9, weight: .medium)) - .foregroundStyle(textSecondary) - } - - VStack(alignment: .trailing, spacing: 2) { - Text("\(trip.totalGames)") - .font(.system(size: 14, weight: .semibold, design: .monospaced)) - .foregroundStyle(textPrimary) - Text("games") - .font(.system(size: 9, weight: .medium)) - .foregroundStyle(textSecondary) - } - } - - // Arrow - Image(systemName: "arrow.right") - .font(.system(size: 10, weight: .medium)) - .foregroundStyle(swissRed) - } - .padding(.vertical, 20) - - // Divider - Rectangle() - .fill(gridLine) - .frame(height: 0.5) - } - } - - // MARK: - Saved Section - - private var savedSection: some View { - VStack(alignment: .leading, spacing: 32) { - HStack(alignment: .top) { - VStack(alignment: .leading, spacing: 8) { - Text("02") - .font(.system(size: 11, weight: .medium, design: .monospaced)) - .foregroundStyle(swissRed) - - Text("Saved") - .font(.system(size: 28, weight: .bold)) - .foregroundStyle(textPrimary) - } - - Spacer() - - Button { - selectedTab = 2 - } label: { - Text("All") - .font(.system(size: 12, weight: .medium)) - .foregroundStyle(textSecondary) - } - } - - Rectangle() - .fill(textPrimary) - .frame(height: 1) - - VStack(spacing: 0) { - ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - VStack(spacing: 0) { - HStack(spacing: 24) { - Text(String(format: "%02d", index + 1)) - .font(.system(size: 12, weight: .medium, design: .monospaced)) - .foregroundStyle(textSecondary) - .frame(width: 24) - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 15, weight: .medium)) - .foregroundStyle(textPrimary) - - Text(trip.formattedDateRange) - .font(.system(size: 11)) - .foregroundStyle(textSecondary) - } - - Spacer() - - Image(systemName: "arrow.right") - .font(.system(size: 10, weight: .medium)) - .foregroundStyle(textSecondary) - } - .padding(.vertical, 20) - - Rectangle() - .fill(gridLine) - .frame(height: 0.5) - } - } - .buttonStyle(.plain) - } - } - } - } - } - - // MARK: - Swiss Footer - - private var swissFooter: some View { - VStack(spacing: 12) { - Rectangle() - .fill(textPrimary) - .frame(width: 40, height: 2) - - Text("SPORTS TIME") - .font(.system(size: 9, weight: .medium)) - .tracking(4) - .foregroundStyle(textSecondary) - } - } -} diff --git a/SportsTime/Features/Home/Views/Variants/Things3/HomeContent_Things3.swift b/SportsTime/Features/Home/Views/Variants/Things3/HomeContent_Things3.swift deleted file mode 100644 index 60e03e8..0000000 --- a/SportsTime/Features/Home/Views/Variants/Things3/HomeContent_Things3.swift +++ /dev/null @@ -1,306 +0,0 @@ -// -// HomeContent_Things3.swift -// SportsTime -// -// THINGS 3-INSPIRED: Ultra-clean task management aesthetic. -// Beautiful spacing, elegant typography, minimalist. -// Focus on clarity and completion. -// - -import SwiftUI -import SwiftData - -struct HomeContent_Things3: View { - @Environment(\.colorScheme) private var colorScheme - @Binding var showNewTrip: Bool - @Binding var selectedTab: Int - @Binding var selectedSuggestedTrip: SuggestedTrip? - - let savedTrips: [SavedTrip] - let suggestedTripsGenerator: SuggestedTripsGenerator - let displayedTips: [PlanningTip] - - // Things 3-inspired colors - private var bgColor: Color { - colorScheme == .dark - ? Color(red: 0.11, green: 0.11, blue: 0.12) - : Color.white - } - - private let thingsBlue = Color(red: 0.35, green: 0.6, blue: 0.95) - private let thingsGray = Color(red: 0.55, green: 0.55, blue: 0.58) - - private var textPrimary: Color { - colorScheme == .dark ? .white : Color(red: 0.15, green: 0.15, blue: 0.17) - } - - private var textSecondary: Color { - colorScheme == .dark ? Color(white: 0.5) : Color(white: 0.5) - } - - private var dividerColor: Color { - colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.9) - } - - var body: some View { - ScrollView { - VStack(spacing: 0) { - // Header - header - .padding(.horizontal, 24) - .padding(.top, 20) - .padding(.bottom, 28) - - // New Trip action - newTripRow - .padding(.horizontal, 24) - - Divider() - .background(dividerColor) - .padding(.horizontal, 24) - .padding(.vertical, 16) - - // Your trips section - if !savedTrips.isEmpty { - tripsSection - .padding(.horizontal, 24) - } - - // Suggestions section - if !suggestedTripsGenerator.suggestedTrips.isEmpty { - suggestionsSection - .padding(.horizontal, 24) - .padding(.top, savedTrips.isEmpty ? 0 : 28) - } - - // Planning tips - if !displayedTips.isEmpty { - TipsSection(tips: displayedTips) - .padding(.horizontal, 24) - .padding(.top, 28) - } - - Spacer(minLength: 60) - } - } - .background(bgColor.ignoresSafeArea()) - } - - // MARK: - Header - - private var header: some View { - VStack(alignment: .leading, spacing: 6) { - Text("Sports Time") - .font(.system(size: 34, weight: .bold)) - .foregroundStyle(textPrimary) - - Text(headerSubtitle) - .font(.system(size: 15)) - .foregroundStyle(textSecondary) - } - .frame(maxWidth: .infinity, alignment: .leading) - } - - private var headerSubtitle: String { - if savedTrips.isEmpty { - return "Plan your first trip" - } else { - let upcoming = savedTrips.filter { ($0.trip?.startDate ?? Date()) > Date() }.count - if upcoming > 0 { - return "\(upcoming) upcoming trip\(upcoming == 1 ? "" : "s")" - } - return "\(savedTrips.count) trip\(savedTrips.count == 1 ? "" : "s") planned" - } - } - - // MARK: - New Trip Row - - private var newTripRow: some View { - Button { - showNewTrip = true - } label: { - HStack(spacing: 14) { - // Checkbox circle - ZStack { - Circle() - .stroke(thingsBlue, lineWidth: 2) - .frame(width: 22, height: 22) - - Image(systemName: "plus") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(thingsBlue) - } - - Text("New Trip") - .font(.system(size: 17)) - .foregroundStyle(thingsBlue) - - Spacer() - } - } - .buttonStyle(.plain) - } - - // MARK: - Trips Section - - private var tripsSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Text("Your Trips") - .font(.system(size: 13, weight: .semibold)) - .foregroundStyle(textSecondary) - .textCase(.uppercase) - .tracking(0.5) - - Spacer() - - if savedTrips.count > 3 { - Button { - selectedTab = 2 - } label: { - Text("See All") - .font(.system(size: 13)) - .foregroundStyle(thingsBlue) - } - } - } - - VStack(spacing: 0) { - ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip) - } label: { - tripRow(trip) - } - .buttonStyle(.plain) - - if index < min(2, savedTrips.count - 1) { - Divider() - .background(dividerColor) - .padding(.leading, 36) - .padding(.vertical, 8) - } - } - } - } - } - } - - private func tripRow(_ trip: Trip) -> some View { - HStack(spacing: 14) { - // Completion circle - Circle() - .stroke(thingsGray.opacity(0.5), lineWidth: 1.5) - .frame(width: 22, height: 22) - - VStack(alignment: .leading, spacing: 4) { - Text(trip.displayName) - .font(.system(size: 17)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - HStack(spacing: 12) { - if let sport = trip.uniqueSports.first { - HStack(spacing: 4) { - Image(systemName: sport.iconName) - .font(.system(size: 11)) - Text(sport.displayName) - .font(.system(size: 13)) - } - .foregroundStyle(sport.themeColor) - } - - Text("•") - .foregroundStyle(textSecondary) - - Text(trip.formattedDateRange) - .font(.system(size: 13)) - .foregroundStyle(textSecondary) - } - } - - Spacer() - - // Count badge - Text("\(trip.totalGames)") - .font(.system(size: 13, weight: .medium)) - .foregroundStyle(textSecondary) - } - } - - // MARK: - Suggestions Section - - private var suggestionsSection: some View { - VStack(alignment: .leading, spacing: 16) { - HStack { - Text("Suggestions") - .font(.system(size: 13, weight: .semibold)) - .foregroundStyle(textSecondary) - .textCase(.uppercase) - .tracking(0.5) - - Spacer() - - Button { - Task { - await suggestedTripsGenerator.refreshTrips() - } - } label: { - Image(systemName: "arrow.clockwise") - .font(.system(size: 13)) - .foregroundStyle(thingsBlue) - } - } - - VStack(spacing: 0) { - ForEach(Array(suggestedTripsGenerator.suggestedTrips.prefix(4).enumerated()), id: \.element.id) { index, suggestedTrip in - Button { - selectedSuggestedTrip = suggestedTrip - } label: { - suggestionRow(suggestedTrip.trip) - } - .buttonStyle(.plain) - - if index < min(3, suggestedTripsGenerator.suggestedTrips.count - 1) { - Divider() - .background(dividerColor) - .padding(.leading, 36) - .padding(.vertical, 8) - } - } - } - } - } - - private func suggestionRow(_ trip: Trip) -> some View { - HStack(spacing: 14) { - // Light gray circle - Circle() - .fill(thingsGray.opacity(0.15)) - .frame(width: 22, height: 22) - .overlay( - Image(systemName: "sparkles") - .font(.system(size: 10)) - .foregroundStyle(thingsGray) - ) - - VStack(alignment: .leading, spacing: 3) { - Text(trip.displayName) - .font(.system(size: 17)) - .foregroundStyle(textPrimary) - .lineLimit(1) - - Text("\(trip.stops.count) stops • \(trip.totalGames) games") - .font(.system(size: 13)) - .foregroundStyle(textSecondary) - } - - Spacer() - - Image(systemName: "chevron.right") - .font(.system(size: 12, weight: .medium)) - .foregroundStyle(thingsGray.opacity(0.5)) - } - } -} diff --git a/SportsTime/Features/Settings/SportsIconImageGenerator.swift b/SportsTime/Features/Settings/SportsIconImageGenerator.swift new file mode 100644 index 0000000..1f33f46 --- /dev/null +++ b/SportsTime/Features/Settings/SportsIconImageGenerator.swift @@ -0,0 +1,287 @@ +// +// SportsIconImageGenerator.swift +// SportsTime +// +// Generates a 1024x1024 PNG with randomly scattered sports icons. +// + +import SwiftUI +import UIKit + +struct SportsIconImageGenerator { + + /// All sports icons used in the animated background + private static let sportsIcons: [String] = [ + // Sports balls + "football.fill", + "basketball.fill", + "baseball.fill", + "hockey.puck.fill", + "soccerball", + "tennisball.fill", + "volleyball.fill", + // Sports figures + "figure.run", + "figure.baseball", + "figure.basketball", + "figure.hockey", + "figure.soccer", + // Venues/events + "sportscourt.fill", + "stadium.fill", + "trophy.fill", + "ticket.fill", + // Travel/navigation + "mappin.circle.fill", + "car.fill", + "map.fill", + "flag.checkered" + ] + + /// Generates a 1024x1024 PNG image with randomly scattered sports icons + /// - Returns: A UIImage with transparent background containing the icons (exactly 1024x1024 pixels) + static func generateImage() -> UIImage { + let size = CGSize(width: 1024, height: 1024) + let maxIconSize: CGFloat = 150 + let minIconSize: CGFloat = 40 + let minSpacing: CGFloat = 80 // Minimum distance between icon centers + + // Use scale 1.0 to ensure exact pixel dimensions (not retina scaled) + let format = UIGraphicsImageRendererFormat() + format.scale = 1.0 + let renderer = UIGraphicsImageRenderer(size: size, format: format) + + // Generate well-distributed positions using Poisson disk sampling + let positions = generateScatteredPositions( + in: size, + minSpacing: minSpacing, + targetCount: 45 + ) + + let image = renderer.image { context in + // Clear background (transparent) + UIColor.clear.setFill() + context.fill(CGRect(origin: .zero, size: size)) + + // Shuffle icons to randomize which icon appears where + let shuffledIcons = sportsIcons.shuffled() + + // Draw icons at scattered positions + for (index, position) in positions.enumerated() { + let iconName = shuffledIcons[index % shuffledIcons.count] + let targetSize = CGFloat.random(in: minIconSize...maxIconSize) + let rotation = CGFloat.random(in: -30...30) * .pi / 180 + + // Create SF Symbol image at a large point size for quality + let config = UIImage.SymbolConfiguration(pointSize: 100, weight: .regular) + if let symbolImage = UIImage(systemName: iconName, withConfiguration: config) { + // Tint with warm orange + let tintedImage = symbolImage.withTintColor( + UIColor(Theme.warmOrange), + renderingMode: .alwaysOriginal + ) + + // Calculate proportional size maintaining aspect ratio + let originalSize = tintedImage.size + let aspectRatio = originalSize.width / originalSize.height + let drawSize: CGSize + if aspectRatio > 1 { + // Wider than tall - constrain by width + drawSize = CGSize(width: targetSize, height: targetSize / aspectRatio) + } else { + // Taller than wide - constrain by height + drawSize = CGSize(width: targetSize * aspectRatio, height: targetSize) + } + + // Save graphics state for rotation + context.cgContext.saveGState() + + // Position at the scattered point + context.cgContext.translateBy(x: position.x, y: position.y) + context.cgContext.rotate(by: rotation) + + // Draw the icon centered at origin with correct aspect ratio + let drawRect = CGRect( + x: -drawSize.width / 2, + y: -drawSize.height / 2, + width: drawSize.width, + height: drawSize.height + ) + tintedImage.draw(in: drawRect) + + context.cgContext.restoreGState() + } + } + } + + return image + } + + /// Generates well-distributed points using a grid-based approach with jitter + /// This ensures icons are scattered across the entire image, not clumped together + private static func generateScatteredPositions( + in size: CGSize, + minSpacing: CGFloat, + targetCount: Int + ) -> [CGPoint] { + var positions: [CGPoint] = [] + + // Calculate grid dimensions to achieve roughly targetCount points + let gridSize = Int(ceil(sqrt(Double(targetCount)))) + let cellWidth = size.width / CGFloat(gridSize) + let cellHeight = size.height / CGFloat(gridSize) + + // Margin from edges + let margin: CGFloat = 40 + + for row in 0.. Data? { + let image = generateImage() + return image.pngData() + } +} + +// MARK: - SwiftUI View for generating and sharing + +struct SportsIconImageGeneratorView: View { + @State private var generatedImage: UIImage? + @State private var isGenerating = false + @State private var showShareSheet = false + + var body: some View { + VStack(spacing: 16) { + // Preview of generated image + if let image = generatedImage { + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 300, maxHeight: 300) + .background( + // Checkerboard pattern to show transparency + CheckerboardPattern() + ) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color.secondary.opacity(0.3), lineWidth: 1) + ) + } + + // Generate button + Button { + generateNewImage() + } label: { + HStack { + if isGenerating { + ProgressView() + .tint(.white) + } else { + Image(systemName: "dice.fill") + } + Text(generatedImage == nil ? "Generate Image" : "Regenerate") + } + .frame(maxWidth: .infinity) + .padding() + .background(Theme.warmOrange) + .foregroundStyle(.white) + .clipShape(RoundedRectangle(cornerRadius: 12)) + } + .disabled(isGenerating) + + // Share button (only visible when image exists) + if generatedImage != nil { + ShareLink( + item: Image(uiImage: generatedImage!), + preview: SharePreview("Sports Icons", image: Image(uiImage: generatedImage!)) + ) { + HStack { + Image(systemName: "square.and.arrow.up") + Text("Share via AirDrop") + } + .frame(maxWidth: .infinity) + .padding() + .background(Color.blue) + .foregroundStyle(.white) + .clipShape(RoundedRectangle(cornerRadius: 12)) + } + } + } + .padding() + } + + private func generateNewImage() { + isGenerating = true + + // Generate on background thread to avoid UI freeze + DispatchQueue.global(qos: .userInitiated).async { + let image = SportsIconImageGenerator.generateImage() + + DispatchQueue.main.async { + withAnimation { + generatedImage = image + isGenerating = false + } + } + } + } +} + +// MARK: - Checkerboard Pattern for transparency preview + +private struct CheckerboardPattern: View { + var body: some View { + Canvas { context, size in + let tileSize: CGFloat = 10 + let rows = Int(ceil(size.height / tileSize)) + let cols = Int(ceil(size.width / tileSize)) + + for row in 0..