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:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user