// // ProgressDesignSamples.swift // SportsTime // // 5 design explorations for stadium progress cards. // All follow the unified design language: solid bg, ghost text, no panels, app icon footer. // #if DEBUG import SwiftUI import UIKit // MARK: - Sample A: "Ring Center" // Giant progress ring centered. Percentage inside the ring. // Sport name above, visited/remaining/trips stats below as plain text. struct ProgressSampleA: View { let progress: LeagueProgress let tripCount: Int let theme: ShareTheme let mapSnapshot: UIImage? private let gold = Color(hex: "FFD700") private var isComplete: Bool { progress.completionPercentage >= 100 } private var accent: Color { isComplete ? gold : theme.accentColor } private var remaining: Int { max(0, progress.totalStadiums - progress.visitedStadiums) } var body: some View { ZStack { (theme.gradientColors.first ?? .black) .ignoresSafeArea() // Ghost percentage Text("\(Int(progress.completionPercentage))%") .font(.system(size: 300, weight: .black, design: .rounded)) .foregroundStyle(theme.textColor.opacity(0.05)) VStack(spacing: 0) { Text(progress.sport.displayName.uppercased()) .font(.system(size: 20, weight: .black)) .tracking(8) .foregroundStyle(theme.secondaryTextColor) .padding(.top, 20) Text("STADIUM QUEST") .font(.system(size: 44, weight: .black)) .foregroundStyle(theme.textColor) .padding(.top, 8) Spacer() // Ring ZStack { Circle() .stroke(theme.textColor.opacity(0.1), lineWidth: 24) .frame(width: 400, height: 400) Circle() .trim(from: 0, to: progress.completionPercentage / 100) .stroke(accent, style: StrokeStyle(lineWidth: 24, lineCap: .round)) .frame(width: 400, height: 400) .rotationEffect(.degrees(-90)) VStack(spacing: 4) { Text("\(progress.visitedStadiums)") .font(.system(size: 108, weight: .black, design: .rounded)) .foregroundStyle(theme.textColor) Text("of \(progress.totalStadiums)") .font(.system(size: 28, weight: .medium)) .foregroundStyle(theme.secondaryTextColor) } } Spacer().frame(height: 50) // Stats as plain text HStack(spacing: 50) { statItem(value: "\(progress.visitedStadiums)", label: "VISITED") statItem(value: "\(remaining)", label: "TO GO") statItem(value: "\(tripCount)", label: "TRIPS") } Spacer().frame(height: 40) // Map if let mapSnapshot { Image(uiImage: mapSnapshot) .resizable() .aspectRatio(contentMode: .fit) .clipShape(RoundedRectangle(cornerRadius: 20)) .frame(height: 350) .padding(.horizontal, 20) } Spacer() ProgressSampleAppFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } private func statItem(value: String, label: String) -> some View { VStack(spacing: 6) { Text(value) .font(.system(size: 44, weight: .black, design: .rounded)) .foregroundStyle(accent) Text(label) .font(.system(size: 14, weight: .bold)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) } } } // MARK: - Sample B: "Big Number" // Massive percentage dominates the card. Thin horizontal progress bar. // Sport icon + name at top. Minimal stats. struct ProgressSampleB: View { let progress: LeagueProgress let tripCount: Int let theme: ShareTheme let mapSnapshot: UIImage? private let gold = Color(hex: "FFD700") private var isComplete: Bool { progress.completionPercentage >= 100 } private var accent: Color { isComplete ? gold : theme.accentColor } private var remaining: Int { max(0, progress.totalStadiums - progress.visitedStadiums) } var body: some View { ZStack { (theme.gradientColors.first ?? .black) .ignoresSafeArea() // Ghost sport name Text(progress.sport.displayName.uppercased()) .font(.system(size: 140, weight: .black)) .foregroundStyle(theme.textColor.opacity(0.04)) VStack(spacing: 0) { // Sport icon + name HStack(spacing: 14) { Image(systemName: progress.sport.iconName) .font(.system(size: 28, weight: .bold)) .foregroundStyle(accent) Text(progress.sport.displayName.uppercased()) .font(.system(size: 20, weight: .black)) .tracking(6) .foregroundStyle(theme.secondaryTextColor) } .padding(.top, 20) Spacer() // Massive percentage HStack(alignment: .firstTextBaseline, spacing: 4) { Text("\(Int(progress.completionPercentage))") .font(.system(size: 200, weight: .black, design: .rounded)) .foregroundStyle(theme.textColor) .minimumScaleFactor(0.5) Text("%") .font(.system(size: 72, weight: .black, design: .rounded)) .foregroundStyle(accent) } Spacer().frame(height: 20) // Thin progress bar GeometryReader { geo in ZStack(alignment: .leading) { Capsule() .fill(theme.textColor.opacity(0.1)) .frame(height: 8) Capsule() .fill(accent) .frame(width: geo.size.width * progress.completionPercentage / 100, height: 8) } } .frame(height: 8) .padding(.horizontal, 60) Spacer().frame(height: 24) Text("\(progress.visitedStadiums) of \(progress.totalStadiums) stadiums") .font(.system(size: 24, weight: .bold)) .foregroundStyle(theme.secondaryTextColor) Spacer().frame(height: 40) // Map if let mapSnapshot { Image(uiImage: mapSnapshot) .resizable() .aspectRatio(contentMode: .fit) .clipShape(RoundedRectangle(cornerRadius: 20)) .frame(height: 380) .padding(.horizontal, 20) } Spacer() ProgressSampleAppFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } // MARK: - Sample C: "Fraction Hero" // "15 of 30" as the hero element, big and centered. // Small ring to the side. Map below. Clean and focused. struct ProgressSampleC: View { let progress: LeagueProgress let tripCount: Int let theme: ShareTheme let mapSnapshot: UIImage? private let gold = Color(hex: "FFD700") private var isComplete: Bool { progress.completionPercentage >= 100 } private var accent: Color { isComplete ? gold : theme.accentColor } private var remaining: Int { max(0, progress.totalStadiums - progress.visitedStadiums) } var body: some View { ZStack { (theme.gradientColors.first ?? .black) .ignoresSafeArea() // Ghost visited count Text("\(progress.visitedStadiums)") .font(.system(size: 400, weight: .black, design: .rounded)) .foregroundStyle(theme.textColor.opacity(0.04)) VStack(spacing: 0) { Text("STADIUM QUEST") .font(.system(size: 18, weight: .black)) .tracking(6) .foregroundStyle(theme.secondaryTextColor) .padding(.top, 20) Spacer() // Hero fraction VStack(spacing: 8) { Text("\(progress.visitedStadiums)") .font(.system(size: 160, weight: .black, design: .rounded)) .foregroundStyle(accent) Rectangle() .fill(theme.textColor.opacity(0.2)) .frame(width: 200, height: 3) Text("\(progress.totalStadiums)") .font(.system(size: 80, weight: .black, design: .rounded)) .foregroundStyle(theme.textColor.opacity(0.4)) } Spacer().frame(height: 20) Text(progress.sport.displayName.uppercased() + " STADIUMS") .font(.system(size: 22, weight: .black)) .tracking(4) .foregroundStyle(theme.secondaryTextColor) if isComplete { Text("COMPLETE") .font(.system(size: 20, weight: .black)) .tracking(6) .foregroundStyle(gold) .padding(.top, 10) } Spacer().frame(height: 40) // Map if let mapSnapshot { Image(uiImage: mapSnapshot) .resizable() .aspectRatio(contentMode: .fit) .clipShape(RoundedRectangle(cornerRadius: 20)) .frame(height: 380) .padding(.horizontal, 20) } Spacer() ProgressSampleAppFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } // MARK: - Sample D: "Map Hero" // Map takes center stage, large. Progress info above and below. // Sport icon badge at top. struct ProgressSampleD: View { let progress: LeagueProgress let tripCount: Int let theme: ShareTheme let mapSnapshot: UIImage? private let gold = Color(hex: "FFD700") private var isComplete: Bool { progress.completionPercentage >= 100 } private var accent: Color { isComplete ? gold : theme.accentColor } private var remaining: Int { max(0, progress.totalStadiums - progress.visitedStadiums) } var body: some View { ZStack { (theme.gradientColors.first ?? .black) .ignoresSafeArea() // Ghost sport name Text(progress.sport.rawValue) .font(.system(size: 200, weight: .black)) .foregroundStyle(theme.textColor.opacity(0.04)) .rotationEffect(.degrees(-15)) VStack(spacing: 0) { // Sport icon badge + title Image(systemName: progress.sport.iconName) .font(.system(size: 44, weight: .bold)) .foregroundStyle(accent) .padding(.top, 20) Text(progress.sport.displayName.uppercased()) .font(.system(size: 20, weight: .black)) .tracking(6) .foregroundStyle(theme.secondaryTextColor) .padding(.top, 10) // Percentage HStack(alignment: .firstTextBaseline, spacing: 4) { Text("\(Int(progress.completionPercentage))") .font(.system(size: 80, weight: .black, design: .rounded)) .foregroundStyle(theme.textColor) Text("% COMPLETE") .font(.system(size: 22, weight: .black)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) } .padding(.top, 16) Spacer().frame(height: 30) // Large map if let mapSnapshot { Image(uiImage: mapSnapshot) .resizable() .aspectRatio(contentMode: .fit) .clipShape(RoundedRectangle(cornerRadius: 24)) .padding(.horizontal, 16) } else { RoundedRectangle(cornerRadius: 24) .fill(theme.textColor.opacity(0.06)) .frame(height: 600) .overlay { Image(systemName: "map") .font(.system(size: 48)) .foregroundStyle(theme.secondaryTextColor) } .padding(.horizontal, 16) } Spacer().frame(height: 30) // Stats below map HStack(spacing: 50) { VStack(spacing: 4) { Text("\(progress.visitedStadiums)") .font(.system(size: 40, weight: .black, design: .rounded)) .foregroundStyle(accent) Text("VISITED") .font(.system(size: 13, weight: .bold)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) } VStack(spacing: 4) { Text("\(remaining)") .font(.system(size: 40, weight: .black, design: .rounded)) .foregroundStyle(theme.textColor) Text("TO GO") .font(.system(size: 13, weight: .bold)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) } VStack(spacing: 4) { Text("\(tripCount)") .font(.system(size: 40, weight: .black, design: .rounded)) .foregroundStyle(theme.textColor) Text("TRIPS") .font(.system(size: 13, weight: .bold)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) } } Spacer() ProgressSampleAppFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } // MARK: - Sample E: "Countdown" // Focus on what's LEFT: "15 TO GO" is the hero. // Visited shown smaller. Optimistic, forward-looking tone. struct ProgressSampleE: View { let progress: LeagueProgress let tripCount: Int let theme: ShareTheme let mapSnapshot: UIImage? private let gold = Color(hex: "FFD700") private var isComplete: Bool { progress.completionPercentage >= 100 } private var accent: Color { isComplete ? gold : theme.accentColor } private var remaining: Int { max(0, progress.totalStadiums - progress.visitedStadiums) } var body: some View { ZStack { (theme.gradientColors.first ?? .black) .ignoresSafeArea() // Ghost remaining number Text("\(remaining)") .font(.system(size: 400, weight: .black, design: .rounded)) .foregroundStyle(accent.opacity(0.06)) VStack(spacing: 0) { HStack(spacing: 14) { Image(systemName: progress.sport.iconName) .font(.system(size: 24, weight: .bold)) .foregroundStyle(accent) Text(progress.sport.displayName.uppercased()) .font(.system(size: 18, weight: .black)) .tracking(4) .foregroundStyle(theme.secondaryTextColor) } .padding(.top, 20) Spacer() if isComplete { // Complete state Text("ALL") .font(.system(size: 48, weight: .black)) .tracking(8) .foregroundStyle(gold) Text("\(progress.totalStadiums)") .font(.system(size: 160, weight: .black, design: .rounded)) .foregroundStyle(theme.textColor) Text("STADIUMS VISITED") .font(.system(size: 24, weight: .black)) .tracking(4) .foregroundStyle(gold) } else { // Countdown state Text("\(remaining)") .font(.system(size: 180, weight: .black, design: .rounded)) .foregroundStyle(theme.textColor) Text("STADIUMS TO GO") .font(.system(size: 24, weight: .black)) .tracking(4) .foregroundStyle(theme.secondaryTextColor) Spacer().frame(height: 20) Text("\(progress.visitedStadiums) of \(progress.totalStadiums) visited") .font(.system(size: 22, weight: .bold)) .foregroundStyle(accent) } Spacer().frame(height: 40) // Map if let mapSnapshot { Image(uiImage: mapSnapshot) .resizable() .aspectRatio(contentMode: .fit) .clipShape(RoundedRectangle(cornerRadius: 20)) .frame(height: 380) .padding(.horizontal, 20) } Spacer() ProgressSampleAppFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } // MARK: - Shared Footer private struct ProgressSampleAppFooter: View { let theme: ShareTheme var body: some View { VStack(spacing: 8) { if let icon = Self.loadAppIcon() { Image(uiImage: icon) .resizable() .aspectRatio(contentMode: .fill) .frame(width: 56, height: 56) .clipShape(RoundedRectangle(cornerRadius: 13)) } Text("SportsTime") .font(.system(size: 18, weight: .bold)) .foregroundStyle(theme.textColor.opacity(0.5)) } .padding(.bottom, 10) } private static func loadAppIcon() -> UIImage? { if let icons = Bundle.main.infoDictionary?["CFBundleIcons"] as? [String: Any], let primary = icons["CFBundlePrimaryIcon"] as? [String: Any], let files = primary["CFBundleIconFiles"] as? [String], let name = files.last { return UIImage(named: name) } return nil } } #endif