212 lines
12 KiB
Markdown
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.
|