Files
Sportstime/docs/CODEX_SHARE_CARD_REDESIGN_PROMPT.md
2026-02-10 18:15:36 -06:00

212 lines
12 KiB
Markdown

# 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.