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

12 KiB

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:

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):

// 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):

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:

xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build

The build must succeed with zero errors.