feat(ui): add 23 home screen design variants with picker

Add design style system with 23 unique home screen aesthetics:
- Classic (original SportsTime design, now default)
- 12 experimental styles (Brutalist, Luxury Editorial, etc.)
- 10 polished app-inspired styles (Flighty, SeatGeek, Apple Maps,
  Things 3, Airbnb, Spotify, Nike Run Club, Fantastical, Strava,
  Carrot Weather)

Includes settings picker to switch between styles and persists
selection via UserDefaults.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-13 14:44:30 -06:00
parent 3d40145ffb
commit 56869ce479
27 changed files with 8636 additions and 27 deletions

View File

@@ -0,0 +1,357 @@
//
// HomeContent_RetroFuturism.swift
// SportsTime
//
// RETRO-FUTURISM: 80s sci-fi meets modern sports tech.
// Neon colors, CRT effects, chrome accents, sports broadcast graphics.
//
import SwiftUI
import SwiftData
struct HomeContent_RetroFuturism: View {
@Environment(\.colorScheme) private var colorScheme
@Binding var showNewTrip: Bool
@Binding var selectedTab: Int
@Binding var selectedSuggestedTrip: SuggestedTrip?
let savedTrips: [SavedTrip]
let suggestedTripsGenerator: SuggestedTripsGenerator
let displayedTips: [PlanningTip]
// Retro color palette
private let neonCyan = Color(red: 0.0, green: 1.0, blue: 0.9)
private let neonMagenta = Color(red: 1.0, green: 0.0, blue: 0.8)
private let neonYellow = Color(red: 1.0, green: 1.0, blue: 0.0)
private let darkBg = Color(red: 0.05, green: 0.0, blue: 0.15)
private let chrome = Color(white: 0.85)
var body: some View {
ZStack {
// Deep dark background
darkBg.ignoresSafeArea()
// Scan lines overlay
scanLinesOverlay
ScrollView {
VStack(spacing: 32) {
// RETRO HEADER
retroHeader
.padding(.top, 24)
// NEON CTA BLOCK
neonCTABlock
.padding(.horizontal, 16)
// FEATURED TRIPS - Broadcast style
if !suggestedTripsGenerator.suggestedTrips.isEmpty {
featuredSection
.padding(.horizontal, 16)
}
// SAVED TRIPS
if !savedTrips.isEmpty {
savedSection
.padding(.horizontal, 16)
}
// RETRO FOOTER
retroFooter
.padding(.bottom, 32)
}
}
}
}
// MARK: - Scan Lines Overlay
private var scanLinesOverlay: some View {
GeometryReader { geo in
VStack(spacing: 2) {
ForEach(0..<Int(geo.size.height / 4), id: \.self) { _ in
Rectangle()
.fill(Color.white.opacity(0.03))
.frame(height: 1)
Rectangle()
.fill(Color.clear)
.frame(height: 3)
}
}
}
.allowsHitTesting(false)
}
// MARK: - Retro Header
private var retroHeader: some View {
VStack(spacing: 8) {
// Chrome accent line
Rectangle()
.fill(
LinearGradient(
colors: [neonCyan.opacity(0), neonCyan, neonCyan.opacity(0)],
startPoint: .leading,
endPoint: .trailing
)
)
.frame(height: 2)
.padding(.horizontal, 40)
// Main title with glow
Text("SPORTS TIME")
.font(.system(size: 36, weight: .black, design: .rounded))
.foregroundStyle(
LinearGradient(
colors: [neonCyan, neonMagenta],
startPoint: .leading,
endPoint: .trailing
)
)
.shadow(color: neonCyan.opacity(0.8), radius: 20, x: 0, y: 0)
.shadow(color: neonMagenta.opacity(0.5), radius: 30, x: 0, y: 0)
// Subtitle
Text("▸ ROAD TRIP COMMAND CENTER ◂")
.font(.system(size: 11, weight: .bold, design: .monospaced))
.foregroundStyle(chrome.opacity(0.7))
.tracking(4)
// Date display - digital clock style
Text(Date.now.formatted(.dateTime.month().day().year()))
.font(.system(size: 14, weight: .medium, design: .monospaced))
.foregroundStyle(neonYellow)
.padding(.horizontal, 16)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 4)
.stroke(neonYellow.opacity(0.5), lineWidth: 1)
)
}
}
// MARK: - Neon CTA Block
private var neonCTABlock: some View {
VStack(spacing: 16) {
// Header bar
HStack {
Text("◆ INITIATE TRIP SEQUENCE ◆")
.font(.system(size: 10, weight: .bold, design: .monospaced))
.foregroundStyle(neonCyan)
Spacer()
}
// Main CTA
Button {
showNewTrip = true
} label: {
HStack {
Image(systemName: "play.fill")
.font(.system(size: 14))
Text("START PLANNING")
.font(.system(size: 16, weight: .black, design: .rounded))
.tracking(2)
Spacer()
Text("")
.font(.system(size: 20, weight: .bold))
}
.foregroundStyle(.black)
.padding(20)
.background(
LinearGradient(
colors: [neonCyan, neonMagenta],
startPoint: .leading,
endPoint: .trailing
)
)
.clipShape(RoundedRectangle(cornerRadius: 8))
.shadow(color: neonCyan.opacity(0.6), radius: 15, x: 0, y: 0)
}
// Status text
Text("SYSTEM READY • ALL STADIUMS ONLINE")
.font(.system(size: 9, weight: .medium, design: .monospaced))
.foregroundStyle(chrome.opacity(0.5))
}
.padding(16)
.background(
RoundedRectangle(cornerRadius: 12)
.stroke(neonCyan.opacity(0.3), lineWidth: 1)
.background(Color.white.opacity(0.03))
)
}
// MARK: - Featured Section
private var featuredSection: some View {
VStack(alignment: .leading, spacing: 16) {
// Broadcast style header
HStack {
Rectangle()
.fill(neonMagenta)
.frame(width: 4, height: 20)
Text("FEATURED TRIPS")
.font(.system(size: 14, weight: .bold, design: .rounded))
.foregroundStyle(chrome)
Spacer()
Text("LIVE")
.font(.system(size: 10, weight: .bold, design: .monospaced))
.foregroundStyle(.black)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(neonMagenta)
.clipShape(RoundedRectangle(cornerRadius: 4))
}
// Trip cards - broadcast ticker style
ForEach(suggestedTripsGenerator.suggestedTrips.prefix(4)) { suggestedTrip in
Button {
selectedSuggestedTrip = suggestedTrip
} label: {
retroTripCard(suggestedTrip.trip)
}
.buttonStyle(.plain)
}
}
}
private func retroTripCard(_ trip: Trip) -> some View {
HStack(spacing: 12) {
// Sport icon with glow
ZStack {
Circle()
.fill(trip.uniqueSports.first?.themeColor.opacity(0.2) ?? neonCyan.opacity(0.2))
.frame(width: 44, height: 44)
Image(systemName: trip.uniqueSports.first?.iconName ?? "sportscourt")
.font(.system(size: 18))
.foregroundStyle(trip.uniqueSports.first?.themeColor ?? neonCyan)
}
.shadow(color: trip.uniqueSports.first?.themeColor.opacity(0.5) ?? neonCyan.opacity(0.5), radius: 10)
VStack(alignment: .leading, spacing: 4) {
Text(trip.name.uppercased())
.font(.system(size: 14, weight: .bold, design: .rounded))
.foregroundStyle(chrome)
HStack(spacing: 8) {
Text("\(trip.stops.count) CITIES")
.font(.system(size: 10, design: .monospaced))
.foregroundStyle(neonCyan)
Text("")
.foregroundStyle(chrome.opacity(0.3))
Text("\(trip.totalGames) GAMES")
.font(.system(size: 10, design: .monospaced))
.foregroundStyle(neonMagenta)
}
}
Spacer()
Image(systemName: "chevron.right")
.font(.system(size: 12, weight: .bold))
.foregroundStyle(neonCyan)
}
.padding(16)
.background(
RoundedRectangle(cornerRadius: 8)
.stroke(
LinearGradient(
colors: [neonCyan.opacity(0.5), neonMagenta.opacity(0.5)],
startPoint: .leading,
endPoint: .trailing
),
lineWidth: 1
)
.background(Color.white.opacity(0.02))
)
}
// MARK: - Saved Section
private var savedSection: some View {
VStack(alignment: .leading, spacing: 16) {
HStack {
Rectangle()
.fill(neonYellow)
.frame(width: 4, height: 20)
Text("YOUR TRIPS")
.font(.system(size: 14, weight: .bold, design: .rounded))
.foregroundStyle(chrome)
Spacer()
Button {
selectedTab = 2
} label: {
Text("VIEW ALL ▸")
.font(.system(size: 10, weight: .bold, design: .monospaced))
.foregroundStyle(neonYellow)
}
}
ForEach(savedTrips.prefix(3)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
} label: {
HStack {
Text(trip.name)
.font(.system(size: 13, weight: .medium, design: .rounded))
.foregroundStyle(chrome.opacity(0.8))
Spacer()
Text("\(trip.stops.count)")
.font(.system(size: 12, design: .monospaced))
.foregroundStyle(neonYellow)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(neonYellow.opacity(0.1))
.clipShape(RoundedRectangle(cornerRadius: 4))
}
.padding(.vertical, 12)
.overlay(alignment: .bottom) {
Rectangle()
.fill(chrome.opacity(0.1))
.frame(height: 1)
}
}
.buttonStyle(.plain)
}
}
}
}
// MARK: - Retro Footer
private var retroFooter: some View {
VStack(spacing: 8) {
Rectangle()
.fill(
LinearGradient(
colors: [neonMagenta.opacity(0), neonMagenta, neonMagenta.opacity(0)],
startPoint: .leading,
endPoint: .trailing
)
)
.frame(height: 1)
.padding(.horizontal, 60)
Text("◀ SPORTS TIME SYSTEMS ▶")
.font(.system(size: 9, weight: .bold, design: .monospaced))
.foregroundStyle(chrome.opacity(0.3))
.tracking(2)
}
}
}