add in icon pxd file

This commit is contained in:
Trey t
2026-01-21 17:59:20 -06:00
parent d97dec44b2
commit 3a135743f8
28 changed files with 347 additions and 8245 deletions

View File

@@ -1,6 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "icon.png",
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024"

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

View File

@@ -2,43 +2,16 @@
// UIDesignStyle.swift // UIDesignStyle.swift
// SportsTime // SportsTime
// //
// 22 distinctive aesthetic variants for the home screen. // UI design style variants for the home screen.
// //
import SwiftUI import SwiftUI
/// Available UI design aesthetics for the home screen /// Available UI design aesthetics for the home screen
enum UIDesignStyle: String, CaseIterable, Identifiable, Codable { enum UIDesignStyle: String, CaseIterable, Identifiable, Codable {
// Default
case classic = "Classic" case classic = "Classic"
case classicAnimated = "Classic Animated" 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 id: String { rawValue }
var description: String { var description: String {
@@ -47,50 +20,6 @@ enum UIDesignStyle: String, CaseIterable, Identifiable, Codable {
return "The original SportsTime design" return "The original SportsTime design"
case .classicAnimated: case .classicAnimated:
return "Classic with animated sports backgrounds" 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 { switch self {
case .classic: return "star.fill" case .classic: return "star.fill"
case .classicAnimated: return "sparkles" 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 { switch self {
case .classic: return Color(red: 1.0, green: 0.45, blue: 0.2) // Warm Orange 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 .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) { func setStyle(_ style: UIDesignStyle) {
currentStyle = style currentStyle = style
} }
/// Convenience property for animation toggle
var animationsEnabled: Bool {
get { currentStyle == .classicAnimated }
set { currentStyle = newValue ? .classicAnimated : .classic }
}
} }
// MARK: - Environment Key // MARK: - Environment Key

View File

@@ -38,226 +38,6 @@ struct AdaptiveHomeContent: View {
suggestedTripsGenerator: suggestedTripsGenerator, suggestedTripsGenerator: suggestedTripsGenerator,
displayedTips: displayedTips 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
)
} }
} }
} }

View File

@@ -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()
}
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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))
}
}
}

View File

@@ -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)
}
}

View File

@@ -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)
)
)
}
}

View File

@@ -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))
}
}
}

View File

@@ -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)
)
}
}

View File

@@ -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, +)
}
}

View File

@@ -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)
}
}
}
}
}
}

View File

@@ -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))
}
}
}

View File

@@ -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)
}
}
}
}
}

View File

@@ -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)
}
}
}
}
}

View File

@@ -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))
)
}
}

View File

@@ -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))
}
}
}

View File

@@ -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))
}
}
}

View File

@@ -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..<Int(geo.size.height / 4), id: \.self) { _ in
Rectangle()
.fill(Color.white.opacity(0.03))
.frame(height: 1)
Rectangle()
.fill(Color.clear)
.frame(height: 3)
}
}
}
.allowsHitTesting(false)
}
// MARK: - Retro Header
private var retroHeader: some View {
VStack(spacing: 8) {
// Chrome accent line
Rectangle()
.fill(
LinearGradient(
colors: [neonCyan.opacity(0), neonCyan, neonCyan.opacity(0)],
startPoint: .leading,
endPoint: .trailing
)
)
.frame(height: 2)
.padding(.horizontal, 40)
// Main title with glow
Text("SPORTS TIME")
.font(.system(size: 36, weight: .black, design: .rounded))
.foregroundStyle(
LinearGradient(
colors: [neonCyan, neonMagenta],
startPoint: .leading,
endPoint: .trailing
)
)
.shadow(color: neonCyan.opacity(0.8), radius: 20, x: 0, y: 0)
.shadow(color: neonMagenta.opacity(0.5), radius: 30, x: 0, y: 0)
// Subtitle
Text("▸ ROAD TRIP COMMAND CENTER ◂")
.font(.system(size: 11, weight: .bold, design: .monospaced))
.foregroundStyle(chrome.opacity(0.7))
.tracking(4)
// Date display - digital clock style
Text(Date.now.formatted(.dateTime.month().day().year()))
.font(.system(size: 14, weight: .medium, design: .monospaced))
.foregroundStyle(neonYellow)
.padding(.horizontal, 16)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 4)
.stroke(neonYellow.opacity(0.5), lineWidth: 1)
)
}
}
// MARK: - Neon CTA Block
private var neonCTABlock: some View {
VStack(spacing: 16) {
// Header bar
HStack {
Text("◆ INITIATE TRIP SEQUENCE ◆")
.font(.system(size: 10, weight: .bold, design: .monospaced))
.foregroundStyle(neonCyan)
Spacer()
}
// Main CTA
Button {
showNewTrip = true
} label: {
HStack {
Image(systemName: "play.fill")
.font(.system(size: 14))
Text("START PLANNING")
.font(.system(size: 16, weight: .black, design: .rounded))
.tracking(2)
Spacer()
Text("")
.font(.system(size: 20, weight: .bold))
}
.foregroundStyle(.black)
.padding(20)
.background(
LinearGradient(
colors: [neonCyan, neonMagenta],
startPoint: .leading,
endPoint: .trailing
)
)
.clipShape(RoundedRectangle(cornerRadius: 8))
.shadow(color: neonCyan.opacity(0.6), radius: 15, x: 0, y: 0)
}
// Status text
Text("SYSTEM READY • ALL STADIUMS ONLINE")
.font(.system(size: 9, weight: .medium, design: .monospaced))
.foregroundStyle(chrome.opacity(0.5))
}
.padding(16)
.background(
RoundedRectangle(cornerRadius: 12)
.stroke(neonCyan.opacity(0.3), lineWidth: 1)
.background(Color.white.opacity(0.03))
)
}
// MARK: - Featured Section
private var featuredSection: some View {
VStack(alignment: .leading, spacing: 16) {
// Broadcast style header
HStack {
Rectangle()
.fill(neonMagenta)
.frame(width: 4, height: 20)
Text("FEATURED TRIPS")
.font(.system(size: 14, weight: .bold, design: .rounded))
.foregroundStyle(chrome)
Spacer()
Text("LIVE")
.font(.system(size: 10, weight: .bold, design: .monospaced))
.foregroundStyle(.black)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(neonMagenta)
.clipShape(RoundedRectangle(cornerRadius: 4))
}
// Trip cards - broadcast ticker style
ForEach(suggestedTripsGenerator.suggestedTrips.prefix(4)) { suggestedTrip in
Button {
selectedSuggestedTrip = suggestedTrip
} label: {
retroTripCard(suggestedTrip.trip)
}
.buttonStyle(.plain)
}
}
}
private func retroTripCard(_ trip: Trip) -> 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)
}
}
}

View File

@@ -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)
}
}

View File

@@ -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))
}
}
}

View File

@@ -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)
}
}

View File

@@ -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)
)
}
}

View File

@@ -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)
}
}
}

View File

@@ -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))
}
}
}

View File

@@ -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..<gridSize {
for col in 0..<gridSize {
// Base position at center of grid cell
let baseCenterX = (CGFloat(col) + 0.5) * cellWidth
let baseCenterY = (CGFloat(row) + 0.5) * cellHeight
// Add random jitter within the cell (up to 40% of cell size)
let jitterX = CGFloat.random(in: -cellWidth * 0.4...cellWidth * 0.4)
let jitterY = CGFloat.random(in: -cellHeight * 0.4...cellHeight * 0.4)
var x = baseCenterX + jitterX
var y = baseCenterY + jitterY
// Clamp to stay within margins
x = max(margin, min(size.width - margin, x))
y = max(margin, min(size.height - margin, y))
// Randomly skip some cells for more organic feel (keep ~80%)
if Double.random(in: 0...1) < 0.85 {
positions.append(CGPoint(x: x, y: y))
}
}
}
// Shuffle to randomize draw order (affects overlap)
return positions.shuffled()
}
/// Generates PNG data from the image
static func generatePNGData() -> 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..<rows {
for col in 0..<cols {
let isLight = (row + col) % 2 == 0
let rect = CGRect(
x: CGFloat(col) * tileSize,
y: CGFloat(row) * tileSize,
width: tileSize,
height: tileSize
)
context.fill(
Path(rect),
with: .color(isLight ? Color.gray.opacity(0.2) : Color.gray.opacity(0.3))
)
}
}
}
}
}
#Preview {
SportsIconImageGeneratorView()
}

View File

@@ -28,8 +28,8 @@ struct SettingsView: View {
// Theme Selection // Theme Selection
themeSection themeSection
// UI Design Style // Home Screen Animations
designStyleSection animationsSection
// Sports Preferences // Sports Preferences
sportsSection sportsSection
@@ -37,6 +37,9 @@ struct SettingsView: View {
// Travel Preferences // Travel Preferences
travelSection travelSection
// Icon Generator
iconGeneratorSection
// About // About
aboutSection aboutSection
@@ -170,54 +173,30 @@ struct SettingsView: View {
.listRowBackground(Theme.cardBackground(colorScheme)) .listRowBackground(Theme.cardBackground(colorScheme))
} }
// MARK: - Design Style Section // MARK: - Animations Section
private var designStyleSection: some View { private var animationsSection: some View {
Section { Section {
ForEach(UIDesignStyle.allCases) { style in Toggle(isOn: Binding(
Button { get: { DesignStyleManager.shared.animationsEnabled },
withAnimation(.easeInOut(duration: 0.2)) { set: { DesignStyleManager.shared.animationsEnabled = $0 }
DesignStyleManager.shared.setStyle(style) )) {
Label {
VStack(alignment: .leading, spacing: 2) {
Text("Animated Background")
.font(.body)
.foregroundStyle(.primary)
Text("Show animated sports graphics on home screen")
.font(.caption)
.foregroundStyle(.secondary)
} }
} label: { } icon: {
HStack(spacing: 12) { Image(systemName: "sparkles")
// Icon with accent color .foregroundStyle(Theme.warmOrange)
ZStack {
Circle()
.fill(style.accentColor.opacity(0.15))
.frame(width: 36, height: 36)
Image(systemName: style.iconName)
.font(.system(size: 16))
.foregroundStyle(style.accentColor)
}
VStack(alignment: .leading, spacing: 2) {
Text(style.rawValue)
.font(.body)
.foregroundStyle(.primary)
Text(style.description)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
Spacer()
if DesignStyleManager.shared.currentStyle == style {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(style.accentColor)
.font(.title3)
}
}
.contentShape(Rectangle())
} }
.buttonStyle(.plain)
} }
} header: { } header: {
Text("Home Screen Style") Text("Home Screen")
} footer: {
Text("Choose a visual aesthetic for the home screen.")
} }
.listRowBackground(Theme.cardBackground(colorScheme)) .listRowBackground(Theme.cardBackground(colorScheme))
} }
@@ -304,6 +283,35 @@ struct SettingsView: View {
.listRowBackground(Theme.cardBackground(colorScheme)) .listRowBackground(Theme.cardBackground(colorScheme))
} }
// MARK: - Icon Generator Section
private var iconGeneratorSection: some View {
Section {
NavigationLink {
SportsIconImageGeneratorView()
.navigationTitle("Icon Generator")
.navigationBarTitleDisplayMode(.inline)
} label: {
Label {
VStack(alignment: .leading, spacing: 2) {
Text("Sports Icon Generator")
.font(.body)
.foregroundStyle(.primary)
Text("Create shareable icon images")
.font(.caption)
.foregroundStyle(.secondary)
}
} icon: {
Image(systemName: "photo.badge.plus")
.foregroundStyle(Theme.warmOrange)
}
}
} header: {
Text("Creative Tools")
}
.listRowBackground(Theme.cardBackground(colorScheme))
}
// MARK: - Reset Section // MARK: - Reset Section
private var resetSection: some View { private var resetSection: some View {