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,427 @@
//
// HomeContent_DarkIndustrial.swift
// SportsTime
//
// DARK INDUSTRIAL: Steel, concrete, utility.
// Stadium infrastructure vibes, warning stripes, exposed structure.
// Functional brutalism with sports facility aesthetics.
//
import SwiftUI
import SwiftData
struct HomeContent_DarkIndustrial: 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]
// Industrial palette
private let warningYellow = Color(red: 1.0, green: 0.8, blue: 0.0)
private let steelGray = Color(red: 0.4, green: 0.45, blue: 0.5)
private let concreteGray = Color(red: 0.3, green: 0.32, blue: 0.35)
private let alertRed = Color(red: 0.9, green: 0.2, blue: 0.2)
private var bgColor: Color {
colorScheme == .dark ? Color(red: 0.08, green: 0.09, blue: 0.1) : Color(red: 0.15, green: 0.16, blue: 0.18)
}
private var textPrimary: Color {
Color(white: 0.9)
}
private var textSecondary: Color {
Color(white: 0.55)
}
var body: some View {
ZStack {
bgColor.ignoresSafeArea()
// Industrial texture overlay
industrialTexture
ScrollView {
VStack(spacing: 0) {
// WARNING STRIPE HEADER
warningStripeHeader
// INDUSTRIAL HERO
industrialHero
.padding(.top, 32)
.padding(.horizontal, 20)
// FEATURED TRIPS
if !suggestedTripsGenerator.suggestedTrips.isEmpty {
featuredSection
.padding(.top, 40)
.padding(.horizontal, 20)
}
// SAVED TRIPS
if !savedTrips.isEmpty {
savedSection
.padding(.top, 40)
.padding(.horizontal, 20)
}
// INDUSTRIAL FOOTER
industrialFooter
.padding(.top, 48)
.padding(.bottom, 32)
}
}
}
}
// MARK: - Industrial Texture
private var industrialTexture: some View {
ZStack {
// Grid pattern (exposed structure)
GeometryReader { geo in
Path { path in
let spacing: CGFloat = 40
for x in stride(from: CGFloat(0), to: geo.size.width, by: spacing) {
path.move(to: CGPoint(x: x, y: 0))
path.addLine(to: CGPoint(x: x, y: geo.size.height))
}
for y in stride(from: CGFloat(0), to: geo.size.height, by: spacing) {
path.move(to: CGPoint(x: 0, y: y))
path.addLine(to: CGPoint(x: geo.size.width, y: y))
}
}
.stroke(steelGray.opacity(0.1), lineWidth: 0.5)
}
// Corner rivets/bolts
VStack {
HStack {
rivetCluster
Spacer()
rivetCluster
}
Spacer()
}
.padding(20)
}
.allowsHitTesting(false)
}
private var rivetCluster: some View {
HStack(spacing: 8) {
rivet
rivet
}
}
private var rivet: some View {
Circle()
.fill(steelGray.opacity(0.3))
.frame(width: 8, height: 8)
.overlay(
Circle()
.fill(steelGray.opacity(0.5))
.frame(width: 4, height: 4)
)
}
// MARK: - Warning Stripe Header
private var warningStripeHeader: some View {
VStack(spacing: 0) {
// Warning stripes
HStack(spacing: 0) {
ForEach(0..<20, id: \.self) { i in
Rectangle()
.fill(i % 2 == 0 ? warningYellow : .black)
.frame(width: 20, height: 8)
}
}
// System status bar
HStack {
HStack(spacing: 6) {
Circle()
.fill(Color.green)
.frame(width: 6, height: 6)
Text("SYSTEM ONLINE")
.font(.system(size: 9, weight: .bold, design: .monospaced))
.foregroundStyle(textSecondary)
}
Spacer()
Text(Date.now.formatted(.dateTime.month().day().year()))
.font(.system(size: 9, weight: .medium, design: .monospaced))
.foregroundStyle(textSecondary)
}
.padding(.horizontal, 16)
.padding(.vertical, 10)
.background(concreteGray.opacity(0.5))
}
}
// MARK: - Industrial Hero
private var industrialHero: some View {
VStack(alignment: .leading, spacing: 24) {
// Sector label
HStack(spacing: 8) {
Rectangle()
.fill(warningYellow)
.frame(width: 4, height: 20)
Text("SECTOR A-1")
.font(.system(size: 10, weight: .bold, design: .monospaced))
.foregroundStyle(warningYellow)
}
// Main title - stencil style
VStack(alignment: .leading, spacing: 4) {
Text("SPORTS")
.font(.system(size: 42, weight: .black))
.foregroundStyle(textPrimary)
Text("TIME")
.font(.system(size: 42, weight: .black))
.foregroundStyle(warningYellow)
}
// Description panel
VStack(alignment: .leading, spacing: 8) {
Text("// TRIP PLANNING SYSTEM")
.font(.system(size: 10, weight: .medium, design: .monospaced))
.foregroundStyle(steelGray)
Text("Route optimization for stadium road trips. Multi-sport scheduling. Real-time coordination.")
.font(.system(size: 14, weight: .regular))
.foregroundStyle(textSecondary)
.lineSpacing(4)
}
.padding(16)
.background(
Rectangle()
.fill(concreteGray.opacity(0.3))
.overlay(
Rectangle()
.stroke(steelGray.opacity(0.3), lineWidth: 1)
)
)
// Industrial CTA
Button {
showNewTrip = true
} label: {
HStack {
Image(systemName: "play.fill")
.font(.system(size: 12))
Text("INITIATE PLANNING")
.font(.system(size: 14, weight: .bold, design: .monospaced))
Spacer()
Text("")
.font(.system(size: 16, weight: .bold))
}
.foregroundStyle(.black)
.padding(18)
.background(warningYellow)
}
}
.padding(24)
.background(
Rectangle()
.fill(bgColor)
.overlay(
Rectangle()
.stroke(steelGray.opacity(0.3), lineWidth: 2)
)
)
}
// MARK: - Featured Section
private var featuredSection: some View {
VStack(alignment: .leading, spacing: 20) {
// Section header
HStack {
HStack(spacing: 8) {
Rectangle()
.fill(alertRed)
.frame(width: 4, height: 20)
Text("FEATURED ROUTES")
.font(.system(size: 12, weight: .bold, design: .monospaced))
.foregroundStyle(textPrimary)
}
Spacer()
Button {
Task {
await suggestedTripsGenerator.refreshTrips()
}
} label: {
HStack(spacing: 4) {
Image(systemName: "arrow.clockwise")
.font(.system(size: 12))
Text("REFRESH")
.font(.system(size: 9, weight: .bold, design: .monospaced))
}
.foregroundStyle(steelGray)
.padding(.horizontal, 12)
.padding(.vertical, 8)
.overlay(
Rectangle()
.stroke(steelGray.opacity(0.5), lineWidth: 1)
)
}
}
// Industrial cards
ForEach(Array(suggestedTripsGenerator.suggestedTrips.prefix(4).enumerated()), id: \.element.id) { index, suggestedTrip in
Button {
selectedSuggestedTrip = suggestedTrip
} label: {
industrialTripCard(suggestedTrip.trip, index: index + 1)
}
.buttonStyle(.plain)
}
}
}
private func industrialTripCard(_ trip: Trip, index: Int) -> some View {
HStack(spacing: 16) {
// Index plate
Text(String(format: "%02d", index))
.font(.system(size: 18, weight: .bold, design: .monospaced))
.foregroundStyle(.black)
.frame(width: 44, height: 44)
.background(warningYellow)
VStack(alignment: .leading, spacing: 6) {
Text(trip.name.uppercased())
.font(.system(size: 13, weight: .bold, design: .monospaced))
.foregroundStyle(textPrimary)
.lineLimit(1)
HStack(spacing: 16) {
HStack(spacing: 4) {
Image(systemName: "mappin")
.font(.system(size: 10))
Text("\(trip.stops.count) STOPS")
.font(.system(size: 10, weight: .medium, design: .monospaced))
}
.foregroundStyle(steelGray)
HStack(spacing: 4) {
Image(systemName: "sportscourt")
.font(.system(size: 10))
Text("\(trip.totalGames) EVENTS")
.font(.system(size: 10, weight: .medium, design: .monospaced))
}
.foregroundStyle(steelGray)
}
}
Spacer()
Image(systemName: "chevron.right")
.font(.system(size: 12, weight: .bold))
.foregroundStyle(steelGray)
}
.padding(16)
.background(
Rectangle()
.fill(concreteGray.opacity(0.2))
.overlay(
Rectangle()
.stroke(steelGray.opacity(0.2), lineWidth: 1)
)
)
}
// MARK: - Saved Section
private var savedSection: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
HStack(spacing: 8) {
Rectangle()
.fill(steelGray)
.frame(width: 4, height: 20)
Text("SAVED ROUTES")
.font(.system(size: 12, weight: .bold, design: .monospaced))
.foregroundStyle(textPrimary)
}
Spacer()
Button {
selectedTab = 2
} label: {
Text("VIEW ALL ▶")
.font(.system(size: 9, weight: .bold, design: .monospaced))
.foregroundStyle(steelGray)
}
}
ForEach(savedTrips.prefix(3)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
} label: {
HStack {
Rectangle()
.fill(steelGray.opacity(0.5))
.frame(width: 3, height: 32)
Text(trip.name.uppercased())
.font(.system(size: 12, weight: .medium, design: .monospaced))
.foregroundStyle(textPrimary)
Spacer()
Text("[\(trip.stops.count)]")
.font(.system(size: 11, weight: .bold, design: .monospaced))
.foregroundStyle(warningYellow)
}
.padding(.vertical, 12)
.overlay(alignment: .bottom) {
Rectangle()
.fill(steelGray.opacity(0.2))
.frame(height: 1)
}
}
.buttonStyle(.plain)
}
}
}
}
// MARK: - Industrial Footer
private var industrialFooter: some View {
VStack(spacing: 12) {
// Warning stripe
HStack(spacing: 0) {
ForEach(0..<8, id: \.self) { i in
Rectangle()
.fill(i % 2 == 0 ? warningYellow.opacity(0.3) : .clear)
.frame(width: 12, height: 4)
}
}
Text("// SPORTS TIME SYSTEMS")
.font(.system(size: 9, weight: .medium, design: .monospaced))
.foregroundStyle(steelGray.opacity(0.5))
}
}
}