// // AchievementDesignSamples.swift // SportsTime // // 5 design explorations for achievement cards. Render all via Debug > Export Samples. // Pick the best parts from each to create the final design. // #if DEBUG import SwiftUI import UIKit // MARK: - Sample Renderer @MainActor struct AchievementSampleRenderer { static func renderAll( achievement: AchievementProgress, milestoneAchievement: AchievementProgress, theme: ShareTheme ) async throws -> [(label: String, image: UIImage)] { var results: [(String, UIImage)] = [] let spotlightViews: [(String, AnyView)] = [ ("A_Broadcast_Spotlight", AnyView(SampleA_Spotlight(achievement: achievement, theme: theme))), ("B_Editorial_Spotlight", AnyView(SampleB_Spotlight(achievement: achievement, theme: theme))), ("C_Trophy_Spotlight", AnyView(SampleC_Spotlight(achievement: achievement, theme: theme))), ("D_Poster_Spotlight", AnyView(SampleD_Spotlight(achievement: achievement, theme: theme))), ("E_Ticket_Spotlight", AnyView(SampleE_Spotlight(achievement: achievement, theme: theme))), ] let milestoneViews: [(String, AnyView)] = [ ("A_Broadcast_Milestone", AnyView(SampleA_Milestone(achievement: milestoneAchievement, theme: theme))), ("B_Editorial_Milestone", AnyView(SampleB_Milestone(achievement: milestoneAchievement, theme: theme))), ("C_Trophy_Milestone", AnyView(SampleC_Milestone(achievement: milestoneAchievement, theme: theme))), ("D_Poster_Milestone", AnyView(SampleD_Milestone(achievement: milestoneAchievement, theme: theme))), ("E_Ticket_Milestone", AnyView(SampleE_Milestone(achievement: milestoneAchievement, theme: theme))), ] for (label, view) in spotlightViews + milestoneViews { let renderer = ImageRenderer(content: view) renderer.scale = 3.0 if let image = renderer.uiImage { results.append((label, image)) } } return results } } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // MARK: - SAMPLE A: "Broadcast" // ESPN-style scoreboard. Colored top banner, badge in a dark panel with // subtle grid behind it, bold condensed name, stat-block earned date. // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ private struct SampleA_Spotlight: View { let achievement: AchievementProgress let theme: ShareTheme var body: some View { ZStack { ShareCardBackground(theme: theme, sports: achievement.sportSet) VStack(spacing: 0) { // Top banner bar HStack { if let sport = achievement.definition.sport { Image(systemName: sport.iconName) .font(.system(size: 28, weight: .bold)) } Text("ACHIEVEMENT UNLOCKED") .font(.system(size: 22, weight: .black)) .tracking(3) Spacer() } .foregroundStyle(.white) .padding(.horizontal, 28) .padding(.vertical, 20) .background(theme.accentColor) Spacer() // Badge in a dark panel with grid ZStack { // Subtle grid GridPatternA() .stroke(theme.textColor.opacity(0.06), lineWidth: 1) SampleBadgeCircle( definition: achievement.definition, size: 360 ) } .frame(height: 500) .padding(.horizontal, 40) .background( RoundedRectangle(cornerRadius: 24) .fill(.black.opacity(0.3)) ) .padding(.horizontal, 40) Spacer().frame(height: 40) // Name — bold condensed Text(achievement.definition.name.uppercased()) .font(.system(size: 58, weight: .black)) .foregroundStyle(theme.textColor) .multilineTextAlignment(.center) .lineLimit(3) .minimumScaleFactor(0.6) .padding(.horizontal, 60) Spacer().frame(height: 16) // Description Text(achievement.definition.description) .font(.system(size: 24, weight: .medium)) .foregroundStyle(theme.secondaryTextColor) .multilineTextAlignment(.center) .padding(.horizontal, 80) Spacer().frame(height: 30) // Stat-block earned date if let date = achievement.earnedAt { HStack(spacing: 40) { VStack(spacing: 4) { Text(date.formatted(.dateTime.month(.abbreviated)).uppercased()) .font(.system(size: 36, weight: .black, design: .rounded)) .foregroundStyle(theme.accentColor) Text("MONTH") .font(.system(size: 14, weight: .bold)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) } Rectangle() .fill(theme.borderColor) .frame(width: 1, height: 50) VStack(spacing: 4) { Text(date.formatted(.dateTime.year())) .font(.system(size: 36, weight: .black, design: .rounded)) .foregroundStyle(theme.accentColor) Text("YEAR") .font(.system(size: 14, weight: .bold)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) } } .padding(.vertical, 20) .padding(.horizontal, 40) .background( RoundedRectangle(cornerRadius: 16) .fill(theme.surfaceColor) .overlay(RoundedRectangle(cornerRadius: 16).stroke(theme.borderColor, lineWidth: 1)) ) } Spacer() ShareCardFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } private struct SampleA_Milestone: View { let achievement: AchievementProgress let theme: ShareTheme private let gold = Color(hex: "FFD700") private let goldDark = Color(hex: "B8860B") var body: some View { ZStack { ShareCardBackground(theme: theme, sports: achievement.sportSet) VStack(spacing: 0) { // Gold milestone banner HStack { Image(systemName: "trophy.fill") .font(.system(size: 24, weight: .bold)) Text("MILESTONE ACHIEVEMENT") .font(.system(size: 20, weight: .black)) .tracking(3) Spacer() } .foregroundStyle(.black) .padding(.horizontal, 28) .padding(.vertical, 18) .background( LinearGradient(colors: [gold, goldDark], startPoint: .leading, endPoint: .trailing) ) Spacer() // Double gold ring around badge ZStack { Circle() .stroke( LinearGradient(colors: [gold, goldDark, gold], startPoint: .topLeading, endPoint: .bottomTrailing), lineWidth: 8 ) .frame(width: 440, height: 440) Circle() .stroke(gold.opacity(0.3), lineWidth: 2) .frame(width: 460, height: 460) SampleBadgeCircle(definition: achievement.definition, size: 400) } Spacer().frame(height: 40) Text(achievement.definition.name.uppercased()) .font(.system(size: 52, weight: .black)) .foregroundStyle(theme.textColor) .multilineTextAlignment(.center) .lineLimit(3) .minimumScaleFactor(0.6) .padding(.horizontal, 60) .padding(.vertical, 16) .background( RoundedRectangle(cornerRadius: 12) .fill(gold.opacity(0.1)) ) Spacer().frame(height: 16) Text(achievement.definition.description) .font(.system(size: 24, weight: .medium)) .foregroundStyle(theme.secondaryTextColor) .multilineTextAlignment(.center) .padding(.horizontal, 80) Spacer() ShareCardFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // MARK: - SAMPLE B: "Editorial" // Magazine-style. Asymmetric, badge on left, name on right in large heavy // type. Thin hairline rules. Generous whitespace. Refined and quiet. // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ private struct SampleB_Spotlight: View { let achievement: AchievementProgress let theme: ShareTheme var body: some View { ZStack { ShareCardBackground(theme: theme, sports: achievement.sportSet) VStack(spacing: 0) { // Minimal top label HStack { Text("ACHIEVEMENT") .font(.system(size: 14, weight: .bold)) .tracking(6) .foregroundStyle(theme.secondaryTextColor) Spacer() if let date = achievement.earnedAt { Text(date.formatted(date: .abbreviated, time: .omitted).uppercased()) .font(.system(size: 14, weight: .bold)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) } } .padding(.bottom, 16) // Hairline Rectangle().fill(theme.borderColor).frame(height: 1) Spacer().frame(height: 60) // Asymmetric: badge left, text right HStack(alignment: .center, spacing: 40) { SampleBadgeMinimal( definition: achievement.definition, size: 280 ) VStack(alignment: .leading, spacing: 20) { Text(achievement.definition.name) .font(.system(size: 52, weight: .heavy)) .foregroundStyle(theme.textColor) .lineLimit(4) .fixedSize(horizontal: false, vertical: true) Rectangle() .fill(theme.accentColor) .frame(width: 60, height: 3) Text(achievement.definition.description) .font(.system(size: 22, weight: .light)) .foregroundStyle(theme.secondaryTextColor) .lineLimit(4) } .frame(maxWidth: .infinity, alignment: .leading) } Spacer() // Bottom: thin rule + category label Rectangle().fill(theme.borderColor).frame(height: 1) HStack { Text(achievement.definition.category.displayName.uppercased()) .font(.system(size: 14, weight: .bold)) .tracking(4) .foregroundStyle(theme.secondaryTextColor) Spacer() if let sport = achievement.definition.sport { Text(sport.rawValue) .font(.system(size: 14, weight: .bold)) .tracking(4) .foregroundStyle(theme.accentColor) } } .padding(.top, 16) .padding(.bottom, 40) ShareCardFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } private struct SampleB_Milestone: View { let achievement: AchievementProgress let theme: ShareTheme private let gold = Color(hex: "FFD700") var body: some View { ZStack { ShareCardBackground(theme: theme, sports: achievement.sportSet) VStack(spacing: 0) { HStack { Text("MILESTONE") .font(.system(size: 14, weight: .bold)) .tracking(6) .foregroundStyle(gold) Spacer() } .padding(.bottom, 16) Rectangle().fill(gold.opacity(0.4)).frame(height: 1) Spacer() // Centered badge, larger SampleBadgeMinimal(definition: achievement.definition, size: 400) .overlay { Circle() .stroke(gold.opacity(0.5), lineWidth: 2) .frame(width: 420, height: 420) } Spacer().frame(height: 50) Text(achievement.definition.name) .font(.system(size: 56, weight: .heavy)) .foregroundStyle(theme.textColor) .multilineTextAlignment(.center) .lineLimit(3) .minimumScaleFactor(0.65) .padding(.horizontal, 40) Rectangle() .fill(gold) .frame(width: 80, height: 3) .padding(.vertical, 20) Text(achievement.definition.description) .font(.system(size: 22, weight: .light)) .foregroundStyle(theme.secondaryTextColor) .multilineTextAlignment(.center) .padding(.horizontal, 80) Spacer() Rectangle().fill(gold.opacity(0.4)).frame(height: 1) .padding(.bottom, 16) ShareCardFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // MARK: - SAMPLE C: "Trophy Case" // Museum display. Badge on a shelf with spotlight from above. Name on a // plaque below. Feels like looking into a glass trophy cabinet. // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ private struct SampleC_Spotlight: View { let achievement: AchievementProgress let theme: ShareTheme var body: some View { ZStack { ShareCardBackground(theme: theme, sports: achievement.sportSet) // Spotlight cone from top RadialGradient( colors: [theme.accentColor.opacity(0.15), .clear], center: UnitPoint(x: 0.5, y: 0.25), startRadius: 10, endRadius: 500 ) VStack(spacing: 0) { Spacer().frame(height: 80) // Top label Text("UNLOCKED") .font(.system(size: 18, weight: .black)) .tracking(8) .foregroundStyle(theme.accentColor) Spacer() // Badge on a "shelf" VStack(spacing: 0) { SampleBadgeAppIcon( definition: achievement.definition, size: 340 ) .shadow(color: .black.opacity(0.4), radius: 20, y: 15) // Shelf line Rectangle() .fill( LinearGradient( colors: [.clear, theme.textColor.opacity(0.3), .clear], startPoint: .leading, endPoint: .trailing ) ) .frame(width: 500, height: 2) .padding(.top, 20) // Shadow under shelf Ellipse() .fill(.black.opacity(0.2)) .frame(width: 300, height: 20) .blur(radius: 8) .offset(y: 4) } Spacer().frame(height: 50) // "Plaque" with name VStack(spacing: 12) { Text(achievement.definition.name) .font(.system(size: 44, weight: .bold)) .foregroundStyle(theme.textColor) .multilineTextAlignment(.center) .lineLimit(3) .minimumScaleFactor(0.65) Text(achievement.definition.description) .font(.system(size: 22, weight: .regular)) .foregroundStyle(theme.secondaryTextColor) .multilineTextAlignment(.center) } .padding(.horizontal, 40) .padding(.vertical, 28) .background( RoundedRectangle(cornerRadius: 16) .fill(theme.surfaceColor) .overlay( RoundedRectangle(cornerRadius: 16) .stroke(theme.accentColor.opacity(0.3), lineWidth: 2) ) ) .padding(.horizontal, 40) if let date = achievement.earnedAt { Text(date.formatted(date: .long, time: .omitted)) .font(.system(size: 18, weight: .medium)) .foregroundStyle(theme.secondaryTextColor) .padding(.top, 20) } Spacer() ShareCardFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } private struct SampleC_Milestone: View { let achievement: AchievementProgress let theme: ShareTheme private let gold = Color(hex: "FFD700") private let goldDark = Color(hex: "B8860B") var body: some View { ZStack { ShareCardBackground(theme: theme, sports: achievement.sportSet) // Gold spotlight RadialGradient( colors: [gold.opacity(0.12), .clear], center: UnitPoint(x: 0.5, y: 0.28), startRadius: 10, endRadius: 600 ) VStack(spacing: 0) { Spacer().frame(height: 60) Text("MILESTONE") .font(.system(size: 22, weight: .black)) .tracking(8) .foregroundStyle(gold) Spacer() // Badge with gold shelf VStack(spacing: 0) { SampleBadgeAppIcon(definition: achievement.definition, size: 380) .shadow(color: gold.opacity(0.3), radius: 30, y: 10) .overlay { RoundedRectangle(cornerRadius: 380 * 0.22) .stroke(gold.opacity(0.6), lineWidth: 3) .frame(width: 390, height: 390) } // Gold shelf Rectangle() .fill( LinearGradient(colors: [.clear, gold.opacity(0.5), .clear], startPoint: .leading, endPoint: .trailing) ) .frame(width: 500, height: 3) .padding(.top, 20) } Spacer().frame(height: 50) // Gold plaque VStack(spacing: 12) { Text(achievement.definition.name) .font(.system(size: 44, weight: .bold)) .foregroundStyle(theme.textColor) .multilineTextAlignment(.center) .lineLimit(3) .minimumScaleFactor(0.65) Text(achievement.definition.description) .font(.system(size: 22, weight: .regular)) .foregroundStyle(theme.secondaryTextColor) .multilineTextAlignment(.center) } .padding(.horizontal, 40) .padding(.vertical, 28) .background( RoundedRectangle(cornerRadius: 16) .fill(gold.opacity(0.08)) .overlay( RoundedRectangle(cornerRadius: 16) .stroke( LinearGradient(colors: [gold, goldDark], startPoint: .top, endPoint: .bottom), lineWidth: 2 ) ) ) .padding(.horizontal, 40) Spacer() ShareCardFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // MARK: - SAMPLE D: "Poster" // Street-poster / hype-beast style. Achievement name is MASSIVE and fills // the card. Badge overlaps the type. Diagonal accent stripe. Rotated text. // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ private struct SampleD_Spotlight: View { let achievement: AchievementProgress let theme: ShareTheme var body: some View { ZStack { ShareCardBackground(theme: theme, sports: achievement.sportSet) // Diagonal accent stripe Rectangle() .fill(theme.accentColor.opacity(0.15)) .frame(width: 300, height: 3000) .rotationEffect(.degrees(-25)) .offset(x: -100) Rectangle() .fill(theme.accentColor.opacity(0.08)) .frame(width: 200, height: 3000) .rotationEffect(.degrees(-25)) .offset(x: 200) VStack(spacing: 0) { // Top: rotated side label HStack { Text("UNLOCKED") .font(.system(size: 14, weight: .black)) .tracking(6) .foregroundStyle(theme.accentColor) .rotationEffect(.degrees(-90)) .fixedSize() Spacer() VStack(alignment: .trailing, spacing: 4) { if let sport = achievement.definition.sport { Text(sport.rawValue) .font(.system(size: 20, weight: .black)) .tracking(3) .foregroundStyle(theme.accentColor) } Text(achievement.definition.category.displayName.uppercased()) .font(.system(size: 14, weight: .bold)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) } } .padding(.top, 20) Spacer() // MASSIVE name ZStack { Text(achievement.definition.name.uppercased()) .font(.system(size: 90, weight: .black)) .foregroundStyle(theme.textColor.opacity(0.08)) .multilineTextAlignment(.center) .lineLimit(4) .minimumScaleFactor(0.4) .padding(.horizontal, 20) // Badge floating on top SampleBadgeDiamond( definition: achievement.definition, size: 320 ) .shadow(color: .black.opacity(0.4), radius: 16, y: 8) } Spacer().frame(height: 30) // Name (readable size) Text(achievement.definition.name) .font(.system(size: 56, weight: .black)) .foregroundStyle(theme.textColor) .multilineTextAlignment(.center) .lineLimit(3) .minimumScaleFactor(0.6) .padding(.horizontal, 40) Spacer().frame(height: 16) Text(achievement.definition.description) .font(.system(size: 22, weight: .medium)) .foregroundStyle(theme.secondaryTextColor) .multilineTextAlignment(.center) .padding(.horizontal, 80) if let date = achievement.earnedAt { Text(date.formatted(date: .abbreviated, time: .omitted).uppercased()) .font(.system(size: 18, weight: .black)) .tracking(3) .foregroundStyle(theme.accentColor) .padding(.top, 16) } Spacer() ShareCardFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } private struct SampleD_Milestone: View { let achievement: AchievementProgress let theme: ShareTheme private let gold = Color(hex: "FFD700") var body: some View { ZStack { ShareCardBackground(theme: theme, sports: achievement.sportSet) // Gold diagonal stripes Rectangle() .fill(gold.opacity(0.1)) .frame(width: 300, height: 3000) .rotationEffect(.degrees(-25)) .offset(x: -100) Rectangle() .fill(gold.opacity(0.06)) .frame(width: 200, height: 3000) .rotationEffect(.degrees(-25)) .offset(x: 200) VStack(spacing: 0) { Text("MILESTONE") .font(.system(size: 20, weight: .black)) .tracking(8) .foregroundStyle(gold) .padding(.top, 20) Spacer() ZStack { Text(achievement.definition.name.uppercased()) .font(.system(size: 90, weight: .black)) .foregroundStyle(gold.opacity(0.06)) .multilineTextAlignment(.center) .lineLimit(4) .minimumScaleFactor(0.4) .padding(.horizontal, 20) SampleBadgeDiamond(definition: achievement.definition, size: 360) .overlay { // Gold diamond border Rectangle() .stroke(gold.opacity(0.5), lineWidth: 3) .frame(width: 270, height: 270) .rotationEffect(.degrees(45)) } .shadow(color: gold.opacity(0.3), radius: 20, y: 8) } Spacer().frame(height: 30) Text(achievement.definition.name) .font(.system(size: 52, weight: .black)) .foregroundStyle(theme.textColor) .multilineTextAlignment(.center) .lineLimit(3) .minimumScaleFactor(0.6) .padding(.horizontal, 40) Spacer().frame(height: 16) Text(achievement.definition.description) .font(.system(size: 22, weight: .medium)) .foregroundStyle(theme.secondaryTextColor) .multilineTextAlignment(.center) .padding(.horizontal, 80) Spacer() ShareCardFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // MARK: - SAMPLE E: "Ticket Stub" // Vintage game-day ticket. Notched edges, dashed tear line, compact info // sections, serial number decoration. Feels like a collectible stub. // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ private struct SampleE_Spotlight: View { let achievement: AchievementProgress let theme: ShareTheme var body: some View { ZStack { ShareCardBackground(theme: theme, sports: achievement.sportSet) VStack(spacing: 0) { Spacer().frame(height: 40) // TICKET — top portion VStack(spacing: 16) { HStack { VStack(alignment: .leading, spacing: 4) { Text("SPORTSTIME ACHIEVEMENTS") .font(.system(size: 14, weight: .black)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) Text("ADMIT ONE") .font(.system(size: 28, weight: .black)) .foregroundStyle(theme.textColor) } Spacer() if let sport = achievement.definition.sport { Text(sport.rawValue) .font(.system(size: 36, weight: .black, design: .rounded)) .foregroundStyle(theme.accentColor) } } // Dashed tear line with notches HStack(spacing: 0) { Circle() .fill(theme.gradientColors.first ?? .black) .frame(width: 30, height: 30) .offset(x: -15) Rectangle() .stroke(theme.textColor.opacity(0.2), style: StrokeStyle(lineWidth: 2, dash: [8, 6])) .frame(height: 2) Circle() .fill(theme.gradientColors.first ?? .black) .frame(width: 30, height: 30) .offset(x: 15) } } .padding(28) .background( RoundedRectangle(cornerRadius: 20) .fill(theme.surfaceColor) .overlay(RoundedRectangle(cornerRadius: 20).stroke(theme.borderColor, lineWidth: 1)) ) Spacer().frame(height: 30) // Main content area VStack(spacing: 24) { SampleBadgeRoundedRect( definition: achievement.definition, size: 300 ) Text(achievement.definition.name) .font(.system(size: 48, weight: .black)) .foregroundStyle(theme.textColor) .multilineTextAlignment(.center) .lineLimit(3) .minimumScaleFactor(0.6) Text(achievement.definition.description) .font(.system(size: 22, weight: .regular)) .foregroundStyle(theme.secondaryTextColor) .multilineTextAlignment(.center) .padding(.horizontal, 40) } Spacer().frame(height: 30) // Bottom stub info HStack { VStack(alignment: .leading, spacing: 4) { Text("DATE") .font(.system(size: 12, weight: .black)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) if let date = achievement.earnedAt { Text(date.formatted(date: .abbreviated, time: .omitted)) .font(.system(size: 20, weight: .bold)) .foregroundStyle(theme.textColor) } } Spacer() VStack(alignment: .center, spacing: 4) { Text("CATEGORY") .font(.system(size: 12, weight: .black)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) Text(achievement.definition.category.displayName.uppercased()) .font(.system(size: 20, weight: .bold)) .foregroundStyle(theme.textColor) } Spacer() VStack(alignment: .trailing, spacing: 4) { Text("NO.") .font(.system(size: 12, weight: .black)) .tracking(2) .foregroundStyle(theme.secondaryTextColor) Text(String(achievement.definition.id.prefix(8)).uppercased()) .font(.system(size: 16, weight: .bold, design: .monospaced)) .foregroundStyle(theme.accentColor) } } .padding(20) .background( RoundedRectangle(cornerRadius: 16) .fill(theme.surfaceColor) .overlay(RoundedRectangle(cornerRadius: 16).stroke(theme.borderColor, lineWidth: 1)) ) // Barcode decoration HStack(spacing: 2) { ForEach(0..<30, id: \.self) { i in Rectangle() .fill(theme.textColor.opacity(0.15)) .frame(width: barWidth(for: i), height: 28) } } .padding(.top, 12) Spacer() ShareCardFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } private func barWidth(for index: Int) -> CGFloat { let widths: [CGFloat] = [3, 2, 4, 2, 3, 5, 2, 3, 2, 4] return widths[index % widths.count] } } private struct SampleE_Milestone: View { let achievement: AchievementProgress let theme: ShareTheme private let gold = Color(hex: "FFD700") var body: some View { ZStack { ShareCardBackground(theme: theme, sports: achievement.sportSet) VStack(spacing: 0) { Spacer().frame(height: 40) // Gold ticket header VStack(spacing: 16) { HStack { VStack(alignment: .leading, spacing: 4) { Text("MILESTONE ACHIEVEMENT") .font(.system(size: 14, weight: .black)) .tracking(2) .foregroundStyle(gold) Text("VIP ACCESS") .font(.system(size: 28, weight: .black)) .foregroundStyle(theme.textColor) } Spacer() Image(systemName: "trophy.fill") .font(.system(size: 32)) .foregroundStyle(gold) } HStack(spacing: 0) { Circle() .fill(theme.gradientColors.first ?? .black) .frame(width: 30, height: 30) .offset(x: -15) Rectangle() .stroke(gold.opacity(0.3), style: StrokeStyle(lineWidth: 2, dash: [8, 6])) .frame(height: 2) Circle() .fill(theme.gradientColors.first ?? .black) .frame(width: 30, height: 30) .offset(x: 15) } } .padding(28) .background( RoundedRectangle(cornerRadius: 20) .fill(theme.surfaceColor) .overlay(RoundedRectangle(cornerRadius: 20).stroke(gold.opacity(0.4), lineWidth: 2)) ) Spacer().frame(height: 30) VStack(spacing: 24) { SampleBadgeRoundedRect(definition: achievement.definition, size: 320) .overlay { RoundedRectangle(cornerRadius: 320 * 0.22) .stroke(gold.opacity(0.5), lineWidth: 3) .frame(width: 330, height: 330) } Text(achievement.definition.name) .font(.system(size: 48, weight: .black)) .foregroundStyle(theme.textColor) .multilineTextAlignment(.center) .lineLimit(3) .minimumScaleFactor(0.6) Text(achievement.definition.description) .font(.system(size: 22, weight: .regular)) .foregroundStyle(theme.secondaryTextColor) .multilineTextAlignment(.center) .padding(.horizontal, 40) } Spacer() ShareCardFooter(theme: theme) } .padding(ShareCardDimensions.padding) } .frame(width: ShareCardDimensions.cardSize.width, height: ShareCardDimensions.cardSize.height) } } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // MARK: - Badge Variants // Each sample gets its own badge shape for maximum variety. // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // A: Classic circle with thick gradient ring private struct SampleBadgeCircle: View { let definition: AchievementDefinition let size: CGFloat var body: some View { ZStack { Circle() .stroke( LinearGradient( colors: [definition.iconColor, definition.iconColor.opacity(0.4)], startPoint: .topLeading, endPoint: .bottomTrailing ), lineWidth: size * 0.04 ) .frame(width: size * 0.92, height: size * 0.92) Circle() .fill( RadialGradient( colors: [definition.iconColor.opacity(0.25), definition.iconColor.opacity(0.05)], center: .center, startRadius: 0, endRadius: size * 0.45 ) ) .frame(width: size * 0.84, height: size * 0.84) Image(systemName: definition.iconName) .font(.system(size: size * 0.4, weight: .bold)) .foregroundStyle(definition.iconColor) } .frame(width: size, height: size) } } // B: Minimal — just color fill, no border, very clean private struct SampleBadgeMinimal: View { let definition: AchievementDefinition let size: CGFloat var body: some View { ZStack { Circle() .fill(definition.iconColor.opacity(0.12)) .frame(width: size, height: size) Image(systemName: definition.iconName) .font(.system(size: size * 0.42, weight: .light)) .foregroundStyle(definition.iconColor) } } } // C: iOS app icon style — rounded square with shadow private struct SampleBadgeAppIcon: View { let definition: AchievementDefinition let size: CGFloat var body: some View { ZStack { RoundedRectangle(cornerRadius: size * 0.22) .fill( LinearGradient( colors: [definition.iconColor.opacity(0.3), definition.iconColor.opacity(0.1)], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .frame(width: size, height: size) RoundedRectangle(cornerRadius: size * 0.22) .stroke(definition.iconColor.opacity(0.5), lineWidth: size * 0.02) .frame(width: size, height: size) Image(systemName: definition.iconName) .font(.system(size: size * 0.42, weight: .semibold)) .foregroundStyle(definition.iconColor) } .shadow(color: .black.opacity(0.2), radius: 8, y: 4) } } // D: Diamond — rotated square private struct SampleBadgeDiamond: View { let definition: AchievementDefinition let size: CGFloat var body: some View { ZStack { Rectangle() .fill(definition.iconColor.opacity(0.15)) .frame(width: size * 0.65, height: size * 0.65) .rotationEffect(.degrees(45)) Rectangle() .stroke(definition.iconColor.opacity(0.6), lineWidth: size * 0.02) .frame(width: size * 0.65, height: size * 0.65) .rotationEffect(.degrees(45)) Image(systemName: definition.iconName) .font(.system(size: size * 0.35, weight: .bold)) .foregroundStyle(definition.iconColor) } .frame(width: size, height: size) } } // E: Rounded rectangle with inner circle — ticket/stamp look private struct SampleBadgeRoundedRect: View { let definition: AchievementDefinition let size: CGFloat var body: some View { ZStack { RoundedRectangle(cornerRadius: size * 0.18) .fill(definition.iconColor.opacity(0.1)) .frame(width: size, height: size) RoundedRectangle(cornerRadius: size * 0.18) .stroke(definition.iconColor.opacity(0.4), lineWidth: size * 0.02) .frame(width: size, height: size) // Inner stamp circle Circle() .stroke(definition.iconColor.opacity(0.2), lineWidth: size * 0.01) .frame(width: size * 0.7, height: size * 0.7) Image(systemName: definition.iconName) .font(.system(size: size * 0.38, weight: .bold)) .foregroundStyle(definition.iconColor) } } } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // MARK: - Support Shapes // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ private struct GridPatternA: Shape { func path(in rect: CGRect) -> Path { var path = Path() let spacing: CGFloat = 40 var x: CGFloat = 0 while x <= rect.width { path.move(to: CGPoint(x: x, y: 0)) path.addLine(to: CGPoint(x: x, y: rect.height)) x += spacing } var y: CGFloat = 0 while y <= rect.height { path.move(to: CGPoint(x: 0, y: y)) path.addLine(to: CGPoint(x: rect.width, y: y)) y += spacing } return path } } // Helper for sport set private extension AchievementProgress { var sportSet: Set { if let sport = definition.sport { return [sport] } return [] } } #endif