# Prompt: Redesign All Shareable Cards — Premium Sports-Media Aesthetic ## Project Context This is an iOS SwiftUI app (`SportsTime.xcodeproj`) for planning multi-stop sports road trips. The app generates shareable image cards (1080x1920 Instagram-story size) for achievements, stadium progress, and trip summaries. These cards are rendered via SwiftUI `ImageRenderer` and exported as UIImages. **Build command:** ```bash xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build ``` ## Files to Modify (all under `SportsTime/Export/Sharing/`) 1. `ShareableContent.swift` — Theme definition and shared types 2. `ShareCardSportBackground.swift` — Card backgrounds 3. `ShareCardComponents.swift` — Reusable header, footer, stats, progress ring, map snapshot generator 4. `AchievementCardGenerator.swift` — 4 achievement card types + badge + confetti 5. `ProgressCardGenerator.swift` — Stadium progress card 6. `TripCardGenerator.swift` — Trip summary card ## Current State (What Exists) The cards currently work but look generic — flat linear gradients, basic system fonts, simple circles, scattered sport icons. The goal is to make them look like ESPN broadcast graphics meets a premium travel app. ### Key Types You'll Reference (defined elsewhere, don't modify): ```swift // Sport enum — .mlb, .nba, .nhl, .nfl, .mls, .wnba, .nwsl // Each has: .iconName (SF Symbol), .rawValue ("MLB"), .displayName, .color // ShareTheme — has: gradientColors, accentColor, textColor, secondaryTextColor, useDarkMap // 8 preset themes: .dark, .light, .midnight, .forest, .sunset, .berry, .ocean, .slate // ShareCardDimensions — cardSize (1080x1920), mapSnapshotSize, routeMapSize, padding (60) // AchievementDefinition — has: name, description, iconName, iconColor, sport, category // AchievementProgress — has: definition, earnedAt (Date?) // LeagueProgress — has: sport, visitedStadiums, totalStadiums, completionPercentage, // stadiumsVisited: [Stadium], stadiumsRemaining: [Stadium] // Trip — has: stops, uniqueSports, cities, totalDistanceMiles, totalGames, formattedDateRange // TripStop — has: city, coordinate, arrivalDate, departureDate // Color(hex:) initializer exists in Theme.swift // Stadium — has: latitude, longitude ``` ## Design Direction **Aesthetic: "Stadium Broadcast"** — bold condensed type, layered depth, geometric sport motifs, premium stat presentation. Each card should feel like a TV broadcast graphic. **Key Principles:** - Strong typographic hierarchy: `.heavy` for headers, `.black` + `.rounded` for numbers, `.light` for descriptions - Three-layer backgrounds: base gradient → geometric pattern → vignette overlay - Sport-specific geometric patterns (diamond lattice for MLB, hexagonal for NBA, ice-crystal for NHL) - "Glass panel" containers: `surfaceColor` fill (textColor at 0.08 opacity) + `borderColor` stroke (textColor at 0.15) - Gold/metallic accents (`#FFD700`, `#B8860B`) for milestones and 100% completion - Tracked ALL CAPS labels for category text - Maps framed with themed borders and subtle shadows ## Detailed Spec Per File ### 1. ShareableContent.swift — Add Theme Helper Extensions Add these computed properties to `ShareTheme` (after the existing `theme(byId:)` method): ```swift var surfaceColor: Color // textColor.opacity(0.08) — glass panel fill var borderColor: Color // textColor.opacity(0.15) — panel borders var glowColor: Color // accentColor.opacity(0.4) — for glow effects var highlightGradient: [Color] // [accentColor, accentColor.opacity(0.6)] var midGradientColor: Color // blend of the two gradient colors — for richer angular gradients ``` You'll also need a `Color.blendedWith(_:fraction:)` helper that interpolates between two colors using `UIColor.getRed`. ### 2. ShareCardSportBackground.swift — Three-Layer Background System Replace the current flat gradient + scattered icons with a `ZStack` of three layers: **Layer 1 — Angular gradient:** Use `AngularGradient` (not linear) with the theme's two gradient colors plus the derived midGradientColor. Overlay a semi-transparent `LinearGradient` to soften it. **Layer 2 — Sport geometric pattern:** Use `Shape` protocol to draw repeating geometric paths: - **MLB:** Diamond/rhombus lattice (rotated squares in a grid, ~60pt spacing, staggered rows) - **NBA/WNBA:** Hexagonal honeycomb (6-sided shapes, ~40pt size) - **NHL:** Angled slash lines (~36pt spacing, rotated ~35 degrees via `CGAffineTransform`) - **NFL/MLS/NWSL/default:** Diagonal stripes (~48pt spacing, 45 degrees) - All rendered at 0.06-0.08 opacity using `theme.textColor` **Layer 3 — Edge vignette:** `RadialGradient` from `.clear` center to `.black.opacity(0.4)` at edges. Start radius ~200, end radius ~900. **Important:** The `ShareCardBackground` wrapper view should now always use `ShareCardSportBackground` (even for no sports — just use the default stripe pattern). This means empty sports set should still get the angular gradient + stripes + vignette. ### 3. ShareCardComponents.swift — Premium Shared Components **ShareCardHeader:** - Sport icon inside a filled `RoundedRectangle(cornerRadius: 18)` (not a Circle), with accent color at 0.25 opacity, 80x80pt, subtle shadow - Title: `.system(size: 44, weight: .heavy, design: .default)` — NOT rounded - Thin accent-colored `Rectangle` below (200pt wide, 2pt tall) **ShareCardFooter:** - Thin horizontal rule above (textColor at 0.15 opacity) - "SPORTSTIME" in `.system(size: 18, weight: .bold)` with `.tracking(6)` in accent color - "Plan your stadium adventure" in `.system(size: 16, weight: .light)` - Compact — no sport icon in the footer **ShareStatsRow — "Broadcast Stats Panel":** - `HStack(spacing: 0)` with each stat taking `maxWidth: .infinity` - Thin vertical `Rectangle` dividers between stats (accent color at 0.4 opacity, 1pt wide, 60pt tall) - Stat value: `.system(size: 48, weight: .black, design: .rounded)` in accent color - Stat label: `.system(size: 16, weight: .medium)` ALL CAPS with `.tracking(2)` in secondaryTextColor - Whole row in a glass panel: `surfaceColor` fill, `borderColor` stroke, cornerRadius 20 **ShareProgressRing:** - `lineWidth: 32` (thicker than before) - Track ring: `surfaceColor` (not just accent at low opacity) - Glow behind progress arc: same arc shape but with `glowColor`, lineWidth + 12, `.blur(radius: 10)` - Tick marks at 25/50/75%: short radial lines on the track ring (textColor at 0.2 opacity) - Center number: `.system(size: 108, weight: .black, design: .rounded)` - "of XX": `.system(size: 28, weight: .light)` **ShareMapSnapshotGenerator (UIKit CoreGraphics drawing):** - Stadium dots: 20pt diameter with 2pt white ring border. Visited ones get a small white checkmark drawn on top. - Route line: 6pt width (was 4pt) - City labels: Draw text size dynamically using `NSString.size(withAttributes:)`, then draw a rounded-rect background sized to fit. Use "START" and "END" labels for first/last stops. Use `.heavy` weight font. ### 4. AchievementCardGenerator.swift — Badge, Confetti, All 4 Card Types **AchievementBadge:** - Outer gradient ring: `LinearGradient` of iconColor → iconColor.opacity(0.5), stroke width = size * 0.025 - Inner fill: `RadialGradient` of iconColor.opacity(0.3) → iconColor.opacity(0.1) - Icon: size * 0.45 (slightly larger than current 0.4) - For earned achievements: thin gold (#FFD700) outer ring at full size - Subtle drop shadow on the whole badge - **New parameter:** `isEarned: Bool` (the old badge only took `definition` and `size`) **ConfettiBurst — "Pyrotechnics":** - 36 particles (was 24) - 4 shape types: Circle, 6-pointed star (custom `StarShape`), Diamond (rotated rect), thin streamer Rectangle - Two burst rings: inner (150-350pt from center) and outer (380-550pt, at 0.6 opacity) - `goldHeavy: Bool` parameter — when true, uses gold palette (#FFD700, #FFC107, #FFE082, #B8860B, #FF6B35) - **Use deterministic seeded random** (hash-based, not `CGFloat.random`) so the layout is consistent across renders. Formula: `abs(seed &* 2654435761) % range` **Spotlight Card:** - Blurred glow circle (glowColor, 420pt, blur 40) behind the badge - Decorative flanking lines: two 100pt accent-colored rectangles with a small circle between them - Achievement name: `.system(size: 52, weight: .heavy)` (NOT rounded) - Description: `.system(size: 26, weight: .light)` - Earned date in a pill: Capsule with surfaceColor fill + borderColor stroke **Milestone Card:** - "MILESTONE" label: `.system(size: 26, weight: .black)`, tracking 6, in gold (#FFD700), with text shadow - Double ring around badge: gold outer (5pt, LinearGradient gold→darkGold), accent inner (3pt) - Badge at 460pt - Name text has a `RoundedRectangle` background with gold gradient (0.15→0.05 opacity) - Confetti with `goldHeavy: true` - Earned date in gold pill (gold text, gold-tinted capsule) **Collection Card:** - Year in huge type: `.system(size: 72, weight: .black, design: .rounded)` in accent color - Sport label below in ALL CAPS tracked secondary text - 3-column `LazyVGrid` with each cell being a glass panel (surfaceColor + borderColor) containing badge (180pt) + name (2 lines) - Bottom: count in accent-tinted pill ("12 UNLOCKED" in tracked bold text) **Context Card:** - Glass header bar (full-width RoundedRectangle, surfaceColor fill): badge (130pt) + name + "UNLOCKED" tracked text - Dotted connection line: 5 small circles vertically (accent at 0.3 opacity) - Map snapshot with shadow - Trip name in a glass banner at bottom ("Unlocked during" light + trip name bold) ### 5. ProgressCardGenerator.swift — Stadium Progress Card - Custom header (not ShareCardHeader): sport icon in rounded rect + "STADIUM QUEST" in `.system(size: 36, weight: .heavy)` with `.tracking(3)` - If 100% complete: "COMPLETED" badge in gold capsule at top - Progress ring (redesigned version) - Below ring: percentage number in `.system(size: 56, weight: .black, design: .rounded)` + "% COMPLETE" tracked - Broadcast stats panel: visited / remain / trips - Map with themed border; gold border if complete - If 100% complete: swap accent color to gold for the progress ring ### 6. TripCardGenerator.swift — Trip Summary Card - Title: "MY [SPORT] ROAD TRIP" (ALL CAPS) via ShareCardHeader - Map: larger frame (maxHeight: 680), with border and shadow - Date range: in a glass pill (Capsule with surfaceColor + borderColor) - Broadcast stats panel: miles / games / cities - City journey trail: horizontal `ScrollView` with numbered city badges - Each city: `HStack` of numbered circle (accent fill, white number) + 3-letter city abbreviation, inside a glass rounded rect - Arrow connectors between cities (SF Symbol `arrow.right`) ## Important Patterns - All views use `ShareCardBackground(theme:sports:)` as background - All views are framed to `ShareCardDimensions.cardSize` (1080x1920) - All card views are `private struct` — only the `*Content` structs are public - Rendering happens via `ImageRenderer(content:)` with `scale = 3.0` - Keep all existing public API signatures unchanged (the `*Content` structs and their `render(theme:)` methods) - Glass panels = surfaceColor fill + borderColor stroke + corner radius 16-20 - `ShareCardSportBackground`, `ShareCardBackground`, `ShareCardHeader`, `ShareCardFooter`, `ShareStatsRow`, `ShareProgressRing`, `ShareMapSnapshotGenerator` are NOT private — they're used across files - Achievement badge, confetti, star shape are private to AchievementCardGenerator ## Verification After implementation, build with: ```bash xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build ``` The build must succeed with zero errors.