# Sharing System Overhaul *Design finalized: January 13, 2026* ## Overview Complete overhaul of the sharing system to support shareable cards for trip summaries, achievements, and stadium progress. Primary target is Instagram Stories (1080×1920 vertical cards) with user-customizable color themes. ## Goals - Share trip summaries as visual cards (not just deep links) - Share achievements (single, collection, milestone, contextual) - Share stadium progress with map visualization - User-customizable color themes (8 presets) - Contextual share buttons throughout the app (low friction) - Instagram Stories as primary destination ## Architecture ### Protocol-Based Design All shareable content types conform to a common protocol: ```swift protocol ShareableContent { var cardType: ShareCardType { get } func render(theme: ShareTheme) async throws -> UIImage } enum ShareCardType { case tripSummary case achievementSpotlight case achievementCollection case achievementMilestone case achievementContext case stadiumProgress } ``` ### Component Hierarchy ``` ShareableContent (protocol) ├── TripShareContent → TripCardGenerator ├── AchievementShareContent → AchievementCardGenerator ├── ProgressShareContent → ProgressCardGenerator └── CollectionShareContent → CollectionCardGenerator ``` ### Unified Components - `ShareTheme` - Color preset definitions - `ShareCardFooter` - Reusable SportsTime branding footer - `ShareCardHeader` - Sport icon + title treatment - `ShareService` - Instagram URL scheme, image export, fallback share sheet ## Theme System ### 8 Color Presets | Theme | Background Gradient | Accent | Text | Use Case | |-------|---------------------|--------|------|----------| | **Dark** | #1A1A2E → #16213E | #FF6B35 | White | Default, premium feel | | **Light** | #FFFFFF → #F5F5F5 | #FF6B35 | #1A1A2E | Clean, minimal | | **Midnight** | #0D1B2A → #1B263B | #00D4FF | White | Night game energy | | **Forest** | #1B4332 → #2D6A4F | #95D5B2 | White | Baseball/outdoor | | **Sunset** | #FF6B35 → #F7931E | #FFFFFF | White | Bold, attention-grabbing | | **Berry** | #4A0E4E → #81267E | #FF85A1 | White | Unique, stands out | | **Ocean** | #023E8A → #0077B6 | #90E0EF | White | Cool, professional | | **Slate** | #2B2D42 → #3D405B | #F4A261 | #EDF2F4 | Sophisticated neutral | ### Theme Definition ```swift struct ShareTheme: Identifiable, Hashable { let id: String let name: String let gradientColors: [Color] let accentColor: Color let textColor: Color let secondaryTextColor: Color let mapStyle: MapStyle // .light or .dark static let all: [ShareTheme] = [.dark, .light, .midnight, .forest, .sunset, .berry, .ocean, .slate] } ``` Theme preference is persisted per content type via UserDefaults. ## Card Designs ### Card Dimensions All cards use Instagram Story dimensions: **1080 × 1920 pixels** (9:16 aspect ratio). ### Trip Summary Card ``` ┌─────────────────────────────────┐ │ [Sport Icon] │ ← Header: 120px │ "My Baseball Road Trip" │ ├─────────────────────────────────┤ │ │ │ ┌─────────────────────┐ │ │ │ │ │ │ │ MAP SNAPSHOT │ │ ← Hero map: 600px │ │ with route line │ │ Route drawn in accent color │ │ + city markers │ │ Visited cities = filled dots │ │ │ │ │ └─────────────────────┘ │ │ │ ├─────────────────────────────────┤ │ "Jun 15 - Jun 22, 2026" │ ← Date range ├─────────────────────────────────┤ │ │ │ ┌───────┐ ┌───────┐ ┌───────┐ │ │ │ 1,847 │ │ 5 │ │ 4 │ │ ← Stats row │ │ miles │ │ games │ │cities │ │ │ └───────┘ └───────┘ └───────┘ │ │ │ ├─────────────────────────────────┤ │ Chicago → Milwaukee → Detroit │ ← City trail │ → Cleveland → Pittsburgh │ ├─────────────────────────────────┤ │ [SportsTime logo] │ ← Footer: 100px │ "Plan your stadium adventure" │ └─────────────────────────────────┘ ``` **Map Features:** - Route line drawn using MKPolyline in accent color - City markers: filled circles with abbreviation labels - Map style matches theme (muted standard for dark themes) ### Achievement Cards #### Type 1: Single Spotlight ``` ┌─────────────────────────────────┐ │ │ │ ┌───────────┐ │ │ │ │ │ │ │ BADGE │ │ ← Large badge: 400×400px │ │ GRAPHIC │ │ │ │ │ │ │ └───────────┘ │ │ │ │ "ALL NL WEST" │ ← Achievement name │ │ │ "Visited all 5 stadiums │ ← Description │ in the National League │ │ West division" │ │ │ │ ✓ Unlocked Jan 12, 2026 │ ← Unlock date │ │ │ [SportsTime logo + tagline] │ └─────────────────────────────────┘ ``` #### Type 2: Collection Grid ``` ┌─────────────────────────────────┐ │ "My 2026 Achievements" │ ← Header with year ├─────────────────────────────────┤ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │Badge│ │Badge│ │Badge│ │ ← 3×3 or 3×4 grid │ │ #1 │ │ #2 │ │ #3 │ │ │ └─────┘ └─────┘ └─────┘ │ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │Badge│ │Badge│ │Badge│ │ │ │ #4 │ │ #5 │ │ #6 │ │ │ └─────┘ └─────┘ └─────┘ │ ├─────────────────────────────────┤ │ "12 achievements unlocked" │ ← Summary count │ [SportsTime logo + tagline] │ └─────────────────────────────────┘ ``` #### Type 3: Milestone Celebration Same as Single Spotlight with: - Animated particle/confetti burst pattern (static for image) - Larger badge (500×500px) - "MILESTONE" label above badge - Gold accent elements regardless of theme #### Type 4: Achievement + Context ``` ┌─────────────────────────────────┐ │ ┌─────────┐ │ │ │ BADGE │ "Coast to Coast" │ ← Badge + name side by side │ └─────────┘ Unlocked! │ ├─────────────────────────────────┤ │ │ │ ┌─────────────────────┐ │ │ │ TRIP MAP or │ │ ← Context: trip/visit │ │ STADIUM PHOTO │ │ that unlocked it │ └─────────────────────┘ │ │ │ │ "Unlocked during my │ │ Summer 2026 Road Trip" │ ← Trip name or visit date │ │ │ [SportsTime logo + tagline] │ └─────────────────────────────────┘ ``` ### Stadium Progress Card ``` ┌─────────────────────────────────┐ │ [Sport Icon] │ │ "MLB Stadium Quest" │ ← Header ├─────────────────────────────────┤ │ │ │ ┌───────────┐ │ │ ╱ ╲ │ │ │ 18 │ │ ← Progress ring: 320px │ │ ──── │ │ Accent color fill │ │ of 30 │ │ │ ╲ ╱ │ │ └───────────┘ │ │ │ │ 60% Complete │ ← Percentage │ │ ├─────────────────────────────────┤ │ ┌───────┐ ┌───────┐ ┌───────┐ │ │ │ 18 │ │ 12 │ │ 4 │ │ ← Stats row │ │visited│ │remain │ │trips │ │ │ └───────┘ └───────┘ └───────┘ │ ├─────────────────────────────────┤ │ ┌─────────────────────┐ │ │ │ │ │ │ │ US MAP SNAPSHOT │ │ ← Map: 500px height │ │ ● visited (accent)│ │ │ │ ○ remaining (gray)│ │ │ │ │ │ │ └─────────────────────┘ │ ├─────────────────────────────────┤ │ @username (optional) │ ← Optional username │ [SportsTime logo + tagline] │ └─────────────────────────────────┘ ``` ## Share UI/UX ### Contextual Share Buttons `ShareButton` component appears on: - Trip cards in saved trips list - Trip detail view toolbar - Achievement badges (on tap) - Progress ring on Progress tab - Achievement list header (for collection) ```swift struct ShareButton: View { let content: ShareableContent @State private var showPreview = false var body: some View { Button { showPreview = true } label: { Image(systemName: "square.and.arrow.up") } .sheet(isPresented: $showPreview) { SharePreviewView(content: content) } } } ``` ### SharePreviewView ``` ┌─────────────────────────────────┐ │ ← Cancel Share → │ ← Nav bar ├─────────────────────────────────┤ │ │ │ ┌─────────────────────┐ │ │ │ │ │ │ │ LIVE PREVIEW │ │ ← 40% scale preview │ │ of card │ │ Updates in real-time │ │ │ │ │ └─────────────────────┘ │ │ │ ├─────────────────────────────────┤ │ Theme │ │ [Dark] [Light] [Midnight] ... │ ← Horizontal scroll ├─────────────────────────────────┤ │ ☑ Include username │ ← Toggle (progress only) │ [@yourhandle____________] │ ├─────────────────────────────────┤ │ │ │ ┌─────────────────────────┐ │ │ │ Share to Instagram │ │ ← Primary CTA │ └─────────────────────────┘ │ │ │ │ [Copy Image] [More Options] │ ← Secondary actions │ │ └─────────────────────────────────┘ ``` ### Instagram Integration ```swift func shareToInstagram(image: UIImage) { guard let imageData = image.pngData() else { return } // Write to shared container for Instagram let pasteboardItems: [String: Any] = [ "com.instagram.sharedSticker.backgroundImage": imageData ] UIPasteboard.general.setItems([pasteboardItems]) // Open Instagram Stories if let url = URL(string: "instagram-stories://share?source_application=com.sportstime.app") { UIApplication.shared.open(url) } } ``` Falls back to standard `UIActivityViewController` if Instagram not installed. ## File Structure ### Files to Delete | File | Lines | |------|-------| | `SportsTime/Export/Services/ProgressCardGenerator.swift` | 607 | ### New Files | File | Purpose | ~Lines | |------|---------|--------| | `Export/Sharing/ShareableContent.swift` | Protocol + ShareTheme | 120 | | `Export/Sharing/ShareCardComponents.swift` | Header, footer, stats, map | 200 | | `Export/Sharing/TripCardGenerator.swift` | Trip summary cards | 180 | | `Export/Sharing/AchievementCardGenerator.swift` | All 4 achievement types | 350 | | `Export/Sharing/ProgressCardGenerator.swift` | Stadium progress cards | 180 | | `Export/Sharing/ShareService.swift` | Instagram, export, share sheet | 150 | | `Export/Views/SharePreviewView.swift` | Unified preview UI | 250 | | `Export/Views/ShareButton.swift` | Contextual button | 40 | ### Files to Modify | File | Changes | |------|---------| | `TripDetailView.swift` | Replace `shareTrip()` with `ShareButton`, remove old `ShareSheet` | | `ProgressTabView.swift` | Add `ShareButton` to progress rings | | Achievement views | Add `ShareButton` to badges | ## Implementation Notes ### Map Snapshot Generation Reuse pattern from existing code but extract to `ShareCardComponents`: ```swift func generateRouteMapSnapshot( stops: [TripStop], theme: ShareTheme ) async -> UIImage? { // Calculate bounding region // Configure MKMapSnapshotter with theme-appropriate style // Draw route polyline in accent color // Draw city markers } func generateProgressMapSnapshot( visited: [Stadium], remaining: [Stadium], theme: ShareTheme ) async -> UIImage? { // Calculate US-wide region // Draw visited markers in accent color // Draw remaining markers in gray } ``` ### Image Rendering Use SwiftUI `ImageRenderer` at 3x scale for high-res output: ```swift func render(theme: ShareTheme) async throws -> UIImage { let cardView = TripCardView(trip: trip, theme: theme) let renderer = ImageRenderer(content: cardView) renderer.scale = 3.0 guard let image = renderer.uiImage else { throw ShareError.renderingFailed } return image } ``` ### Theme Persistence ```swift @AppStorage("shareTheme.trip") private var tripThemeId: String = "dark" @AppStorage("shareTheme.achievement") private var achievementThemeId: String = "dark" @AppStorage("shareTheme.progress") private var progressThemeId: String = "dark" ``` ## Scope Summary | Category | Count | |----------|-------| | New lines | ~1,470 | | Deleted lines | ~607 | | Modified lines | ~50 | | New files | 8 | | Deleted files | 1 | | Modified files | 3+ | ## Success Criteria - [ ] All 3 content types shareable (trips, achievements, progress) - [ ] All 8 theme presets working - [ ] Instagram Stories direct share working - [ ] Fallback to share sheet when Instagram not installed - [ ] Share buttons appear contextually throughout app - [ ] Theme preference persisted per content type - [ ] Cards render at 1080×1920 with proper scaling