# Brutalist App-Wide Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Extend the Brutalist design style from home-screen-only to work app-wide (TripDetailView, ScheduleListView, SavedTripsListView, SettingsView). **Architecture:** Adaptive view routers that switch between Classic and Brutalist variants based on `DesignStyleManager.shared.currentStyle`. StyleProvider protocol provides shared styling constants. Brutalist uses Theme colors but with monospace fonts, sharp corners, and borders. **Tech Stack:** SwiftUI, @Observable pattern, Theme system, DesignStyleManager --- ## Task 1: Create StyleProvider Protocol **Files:** - Create: `SportsTime/Core/Design/StyleProvider.swift` **Step 1: Create the StyleProvider file** ```swift // // StyleProvider.swift // SportsTime // // Provides style-specific constants for shared components. // import SwiftUI /// Protocol for style-specific visual properties protocol StyleProvider { // Shape var cornerRadius: CGFloat { get } var borderWidth: CGFloat { get } // Typography var fontDesign: Font.Design { get } var usesUppercase: Bool { get } var headerTracking: CGFloat { get } // Effects var usesGradientBackgrounds: Bool { get } var usesSoftShadows: Bool { get } // Helpers func flatBackground(_ colorScheme: ColorScheme) -> Color } // MARK: - Classic Style struct ClassicStyle: StyleProvider { let cornerRadius: CGFloat = 12 let borderWidth: CGFloat = 0 let fontDesign: Font.Design = .default let usesUppercase = false let headerTracking: CGFloat = 0 let usesGradientBackgrounds = true let usesSoftShadows = true func flatBackground(_ colorScheme: ColorScheme) -> Color { Theme.cardBackground(colorScheme) } } // MARK: - Brutalist Style struct BrutalistStyle: StyleProvider { let cornerRadius: CGFloat = 0 let borderWidth: CGFloat = 1 let fontDesign: Font.Design = .monospaced let usesUppercase = true let headerTracking: CGFloat = 2 let usesGradientBackgrounds = false let usesSoftShadows = false func flatBackground(_ colorScheme: ColorScheme) -> Color { colorScheme == .dark ? .black : Color(white: 0.95) } } // MARK: - UIDesignStyle Extension extension UIDesignStyle { /// Returns the appropriate StyleProvider for this design style var styleProvider: StyleProvider { switch self { case .brutalist: return BrutalistStyle() default: return ClassicStyle() } } } ``` **Step 2: Verify the file compiles** Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -20` Expected: BUILD SUCCEEDED **Step 3: Commit** ```bash git add SportsTime/Core/Design/StyleProvider.swift git commit -m "$(cat <<'EOF' feat(design): add StyleProvider protocol for style-specific constants Introduces ClassicStyle and BrutalistStyle implementations for shared component styling across the app. Co-Authored-By: Claude Opus 4.5 EOF )" ``` --- ## Task 2: Create TripDetailView Brutalist Variant **Files:** - Create: `SportsTime/Features/Trip/Views/Variants/Brutalist/TripDetailView_Brutalist.swift` - Create: `SportsTime/Features/Trip/Views/Variants/Classic/TripDetailView_Classic.swift` - Create: `SportsTime/Features/Trip/Views/AdaptiveTripDetailView.swift` - Modify: `SportsTime/Features/Trip/Views/TripDetailView.swift` (extract to Classic) ### Step 1: Create directory structure ```bash mkdir -p SportsTime/Features/Trip/Views/Variants/Classic mkdir -p SportsTime/Features/Trip/Views/Variants/Brutalist ``` ### Step 2: Create AdaptiveTripDetailView router Create `SportsTime/Features/Trip/Views/AdaptiveTripDetailView.swift`: ```swift // // AdaptiveTripDetailView.swift // SportsTime // // Routes to the appropriate trip detail variant based on the selected design style. // import SwiftUI struct AdaptiveTripDetailView: View { let trip: Trip let games: [String: RichGame]? /// Initialize with trip and games dictionary init(trip: Trip, games: [String: RichGame]) { self.trip = trip self.games = games } /// Initialize with just trip - games will be loaded from AppDataProvider init(trip: Trip) { self.trip = trip self.games = nil } var body: some View { switch DesignStyleManager.shared.currentStyle { case .brutalist: if let games = games { TripDetailView_Brutalist(trip: trip, games: games) } else { TripDetailView_Brutalist(trip: trip) } default: if let games = games { TripDetailView_Classic(trip: trip, games: games) } else { TripDetailView_Classic(trip: trip) } } } } ``` ### Step 3: Copy existing TripDetailView to Classic variant Copy `TripDetailView.swift` to `Variants/Classic/TripDetailView_Classic.swift` and rename the struct to `TripDetailView_Classic`. Key changes: - Rename `struct TripDetailView` → `struct TripDetailView_Classic` - Rename `struct DaySection` → `struct DaySection_Classic` - Rename `struct GameRow` → `struct GameRow_Classic` - Rename `struct TravelSection` → `struct TravelSection_Classic` - Rename `struct EVChargerRow` → `struct EVChargerRow_Classic` - Keep `struct ShareSheet` as-is (shared utility) - Keep `enum ItinerarySection` as-is (shared model) - Remove the #Preview at the bottom ### Step 4: Create TripDetailView_Brutalist Create `SportsTime/Features/Trip/Views/Variants/Brutalist/TripDetailView_Brutalist.swift`: ```swift // // TripDetailView_Brutalist.swift // SportsTime // // BRUTALIST: Raw, unpolished, anti-design trip details. // Monospace typography, harsh borders, ticket stub aesthetics. // import SwiftUI import SwiftData import MapKit struct TripDetailView_Brutalist: View { @Environment(\.modelContext) private var modelContext @Environment(\.colorScheme) private var colorScheme let trip: Trip private let providedGames: [String: RichGame]? @Query private var savedTrips: [SavedTrip] @State private var showProPaywall = false @State private var showExportSheet = false @State private var exportURL: URL? @State private var isExporting = false @State private var exportProgress: PDFAssetPrefetcher.PrefetchProgress? @State private var mapCameraPosition: MapCameraPosition = .automatic @State private var isSaved = false @State private var routePolylines: [MKPolyline] = [] @State private var isLoadingRoutes = false @State private var loadedGames: [String: RichGame] = [:] @State private var isLoadingGames = false private let exportService = ExportService() private let dataProvider = AppDataProvider.shared private var games: [String: RichGame] { providedGames ?? loadedGames } private var bgColor: Color { colorScheme == .dark ? .black : Color(white: 0.95) } private var textColor: Color { colorScheme == .dark ? .white : .black } init(trip: Trip, games: [String: RichGame]) { self.trip = trip self.providedGames = games } init(trip: Trip) { self.trip = trip self.providedGames = nil } var body: some View { ScrollView { VStack(alignment: .leading, spacing: 0) { // HERO MAP - Sharp edges heroMapSection .frame(height: 280) // HEADER tripHeader .padding(.horizontal, 16) .padding(.top, 24) perforatedDivider .padding(.vertical, 24) // STATS ROW statsRow .padding(.horizontal, 16) perforatedDivider .padding(.vertical, 24) // SCORE CARD if let score = trip.score { scoreCard(score) .padding(.horizontal, 16) perforatedDivider .padding(.vertical, 24) } // ITINERARY itinerarySection .padding(.horizontal, 16) .padding(.bottom, 32) } } .background(bgColor) .toolbar { ToolbarItemGroup(placement: .primaryAction) { ShareButton(trip: trip, style: .icon) .foregroundStyle(Theme.warmOrange) Button { if StoreManager.shared.isPro { Task { await exportPDF() } } else { showProPaywall = true } } label: { HStack(spacing: 2) { Image(systemName: "doc.fill") if !StoreManager.shared.isPro { ProBadge() } } .foregroundStyle(Theme.warmOrange) } } } .sheet(isPresented: $showExportSheet) { if let url = exportURL { ShareSheet(items: [url]) } } .sheet(isPresented: $showProPaywall) { PaywallView() } .onAppear { checkIfSaved() } .task { await loadGamesIfNeeded() } .overlay { if isExporting { exportProgressOverlay } } } // MARK: - Perforated Divider private var perforatedDivider: some View { HStack(spacing: 8) { ForEach(0..<25, id: \.self) { _ in Circle() .fill(textColor.opacity(0.3)) .frame(width: 6, height: 6) } } .frame(maxWidth: .infinity) .padding(.horizontal, 16) } // MARK: - Hero Map Section private var heroMapSection: some View { ZStack(alignment: .topTrailing) { Map(position: $mapCameraPosition, interactionModes: []) { ForEach(stopCoordinates.indices, id: \.self) { index in let stop = stopCoordinates[index] Annotation(stop.name, coordinate: stop.coordinate) { Rectangle() .fill(index == 0 ? Theme.warmOrange : Theme.routeGold) .frame(width: 12, height: 12) } } ForEach(routePolylines.indices, id: \.self) { index in MapPolyline(routePolylines[index]) .stroke(Theme.routeGold, lineWidth: 3) } } .mapStyle(.standard(elevation: .flat)) // Save button - sharp rectangle Button { toggleSaved() } label: { Image(systemName: isSaved ? "heart.fill" : "heart") .font(.title3) .foregroundStyle(isSaved ? .red : textColor) .padding(12) .background(bgColor) .border(textColor.opacity(0.3), width: 1) } .padding(12) } .task { updateMapRegion() await fetchDrivingRoutes() } } // MARK: - Header private var tripHeader: some View { VStack(alignment: .leading, spacing: 8) { // Date range Text(trip.formattedDateRange.uppercased()) .font(.system(.caption, design: .monospaced)) .foregroundStyle(textColor.opacity(0.5)) .tracking(2) // Route as arrow chain Text(trip.stops.map { $0.city.uppercased() }.joined(separator: " → ")) .font(.system(.title3, design: .monospaced).bold()) .foregroundStyle(textColor) .lineLimit(2) // Sport badges - bordered rectangles HStack(spacing: 8) { ForEach(Array(trip.uniqueSports), id: \.self) { sport in HStack(spacing: 4) { Image(systemName: sport.iconName) .font(.caption2) Text(sport.rawValue.uppercased()) .font(.system(.caption2, design: .monospaced)) } .padding(.horizontal, 10) .padding(.vertical, 6) .foregroundStyle(sport.themeColor) .border(sport.themeColor.opacity(0.5), width: 1) } } } } // MARK: - Stats Row private var statsRow: some View { HStack(spacing: 12) { brutalistStat(value: "\(trip.tripDuration)", label: "DAYS") brutalistStat(value: "\(trip.stops.count)", label: "CITIES") brutalistStat(value: "\(trip.totalGames)", label: "GAMES") } } private func brutalistStat(value: String, label: String) -> some View { VStack(spacing: 4) { Text(value) .font(.system(.title, design: .monospaced).bold()) .foregroundStyle(textColor) Text("[ \(label) ]") .font(.system(.caption2, design: .monospaced)) .foregroundStyle(textColor.opacity(0.5)) } .frame(maxWidth: .infinity) .padding(.vertical, 16) .border(textColor.opacity(0.2), width: 1) } // MARK: - Score Card private func scoreCard(_ score: TripScore) -> some View { VStack(spacing: 16) { HStack { Text("[ TRIP SCORE ]") .font(.system(.caption, design: .monospaced)) .foregroundStyle(textColor.opacity(0.5)) Spacer() Text(score.scoreGrade) .font(.system(size: 48, weight: .black, design: .monospaced)) .foregroundStyle(Theme.warmOrange) } HStack(spacing: 8) { scoreItem(label: "GAMES", value: score.gameQualityScore) scoreItem(label: "ROUTE", value: score.routeEfficiencyScore) scoreItem(label: "BALANCE", value: score.leisureBalanceScore) scoreItem(label: "PREFS", value: score.preferenceAlignmentScore) } } .padding(16) .border(textColor.opacity(0.2), width: 2) } private func scoreItem(label: String, value: Double) -> some View { VStack(spacing: 4) { Text(String(format: "%.0f", value)) .font(.system(.headline, design: .monospaced).bold()) .foregroundStyle(textColor) Text(label) .font(.system(.caption2, design: .monospaced)) .foregroundStyle(textColor.opacity(0.4)) } .frame(maxWidth: .infinity) } // MARK: - Itinerary private var itinerarySection: some View { VStack(alignment: .leading, spacing: 16) { Text("[ ITINERARY ]") .font(.system(.caption, design: .monospaced)) .foregroundStyle(textColor.opacity(0.5)) if isLoadingGames { Text("LOADING...") .font(.system(.body, design: .monospaced)) .foregroundStyle(textColor.opacity(0.5)) .frame(maxWidth: .infinity) .padding(.vertical, 32) } else { ForEach(Array(itinerarySections.enumerated()), id: \.offset) { index, section in switch section { case .day(let dayNumber, let date, let gamesOnDay): DaySection_Brutalist( dayNumber: dayNumber, date: date, games: gamesOnDay ) case .travel(let segment): TravelSection_Brutalist(segment: segment) } } } } } // MARK: - Export Progress Overlay private var exportProgressOverlay: some View { ZStack { Color.black.opacity(0.8) .ignoresSafeArea() VStack(spacing: 16) { Text("CREATING PDF") .font(.system(.headline, design: .monospaced)) .foregroundStyle(.white) Text(exportProgress?.currentStep.uppercased() ?? "PREPARING...") .font(.system(.caption, design: .monospaced)) .foregroundStyle(.white.opacity(0.7)) if let progress = exportProgress { Text("\(Int(progress.percentComplete * 100))%") .font(.system(.title, design: .monospaced).bold()) .foregroundStyle(Theme.warmOrange) } } .padding(32) .border(Color.white.opacity(0.3), width: 2) } } // MARK: - Computed Properties (same as Classic) private var stopCoordinates: [(name: String, coordinate: CLLocationCoordinate2D)] { trip.stops.compactMap { stop -> (String, CLLocationCoordinate2D)? in if let coord = stop.coordinate { return (stop.city, coord) } if let stadiumId = stop.stadium, let stadium = dataProvider.stadium(for: stadiumId) { return (stadium.name, stadium.coordinate) } return nil } } private var itinerarySections: [ItinerarySection] { // Same logic as Classic variant var sections: [ItinerarySection] = [] var dayCitySections: [(dayNumber: Int, date: Date, city: String, games: [RichGame])] = [] let days = tripDays for (index, dayDate) in days.enumerated() { let dayNum = index + 1 let gamesOnDay = gamesOn(date: dayDate) guard !gamesOnDay.isEmpty else { continue } var gamesByCity: [(city: String, games: [RichGame])] = [] for game in gamesOnDay { let city = game.stadium.city if let lastIndex = gamesByCity.indices.last, gamesByCity[lastIndex].city == city { gamesByCity[lastIndex].games.append(game) } else { gamesByCity.append((city, [game])) } } for cityGroup in gamesByCity { dayCitySections.append((dayNum, dayDate, cityGroup.city, cityGroup.games)) } } for (index, section) in dayCitySections.enumerated() { if index > 0 { let prevCity = dayCitySections[index - 1].city let currentCity = section.city if !prevCity.isEmpty && !currentCity.isEmpty && prevCity != currentCity { if let travelSegment = findTravelSegment(from: prevCity, to: currentCity) { sections.append(.travel(travelSegment)) } } } sections.append(.day(dayNumber: section.dayNumber, date: section.date, games: section.games)) } return sections } private var tripDays: [Date] { let calendar = Calendar.current guard let startDate = trip.stops.first?.arrivalDate, let endDate = trip.stops.last?.departureDate else { return [] } var days: [Date] = [] var current = calendar.startOfDay(for: startDate) let end = calendar.startOfDay(for: endDate) while current <= end { days.append(current) current = calendar.date(byAdding: .day, value: 1, to: current)! } return days } private func gamesOn(date: Date) -> [RichGame] { let calendar = Calendar.current let dayStart = calendar.startOfDay(for: date) let allGameIds = trip.stops.flatMap { $0.games } let foundGames = allGameIds.compactMap { games[$0] } return foundGames.filter { richGame in calendar.startOfDay(for: richGame.game.dateTime) == dayStart }.sorted { $0.game.dateTime < $1.game.dateTime } } private func findTravelSegment(from fromCity: String, to toCity: String) -> TravelSegment? { let fromLower = fromCity.lowercased().trimmingCharacters(in: .whitespaces) let toLower = toCity.lowercased().trimmingCharacters(in: .whitespaces) return trip.travelSegments.first { segment in let segmentFrom = segment.fromLocation.name.lowercased().trimmingCharacters(in: .whitespaces) let segmentTo = segment.toLocation.name.lowercased().trimmingCharacters(in: .whitespaces) return segmentFrom == fromLower && segmentTo == toLower } } // MARK: - Actions (same as Classic) private func loadGamesIfNeeded() async { guard providedGames == nil else { return } let gameIds = trip.stops.flatMap { $0.games } guard !gameIds.isEmpty else { return } isLoadingGames = true var loaded: [String: RichGame] = [:] for gameId in gameIds { if let game = try? await dataProvider.fetchGame(by: gameId), let richGame = dataProvider.richGame(from: game) { loaded[gameId] = richGame } } loadedGames = loaded isLoadingGames = false } private func exportPDF() async { isExporting = true exportProgress = nil do { let url = try await exportService.exportToPDF(trip: trip, games: games) { progress in await MainActor.run { self.exportProgress = progress } } exportURL = url showExportSheet = true } catch { } isExporting = false } private func toggleSaved() { if isSaved { unsaveTrip() } else { saveTrip() } } private func saveTrip() { if !StoreManager.shared.isPro && savedTrips.count >= StoreManager.freeTripLimit { showProPaywall = true return } guard let savedTrip = SavedTrip.from(trip, games: games, status: .planned) else { return } modelContext.insert(savedTrip) if let _ = try? modelContext.save() { withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) { isSaved = true } } } private func unsaveTrip() { let tripId = trip.id let descriptor = FetchDescriptor(predicate: #Predicate { $0.id == tripId }) if let savedTrips = try? modelContext.fetch(descriptor) { for savedTrip in savedTrips { modelContext.delete(savedTrip) } try? modelContext.save() withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) { isSaved = false } } } private func checkIfSaved() { let tripId = trip.id let descriptor = FetchDescriptor(predicate: #Predicate { $0.id == tripId }) if let count = try? modelContext.fetchCount(descriptor), count > 0 { isSaved = true } } private func updateMapRegion() { guard !stopCoordinates.isEmpty else { return } let coordinates = stopCoordinates.map(\.coordinate) let lats = coordinates.map(\.latitude) let lons = coordinates.map(\.longitude) guard let minLat = lats.min(), let maxLat = lats.max(), let minLon = lons.min(), let maxLon = lons.max() else { return } let center = CLLocationCoordinate2D( latitude: (minLat + maxLat) / 2, longitude: (minLon + maxLon) / 2 ) let latSpan = (maxLat - minLat) * 1.3 + 0.5 let lonSpan = (maxLon - minLon) * 1.3 + 0.5 mapCameraPosition = .region(MKCoordinateRegion( center: center, span: MKCoordinateSpan(latitudeDelta: max(latSpan, 1), longitudeDelta: max(lonSpan, 1)) )) } private func fetchDrivingRoutes() async { let stops = stopCoordinates guard stops.count >= 2 else { return } isLoadingRoutes = true var polylines: [MKPolyline] = [] for i in 0..<(stops.count - 1) { let source = stops[i] let destination = stops[i + 1] let request = MKDirections.Request() let sourceLocation = CLLocation(latitude: source.coordinate.latitude, longitude: source.coordinate.longitude) let destLocation = CLLocation(latitude: destination.coordinate.latitude, longitude: destination.coordinate.longitude) request.source = MKMapItem(location: sourceLocation, address: nil) request.destination = MKMapItem(location: destLocation, address: nil) request.transportType = .automobile let directions = MKDirections(request: request) do { let response = try await directions.calculate() if let route = response.routes.first { polylines.append(route.polyline) } } catch { let straightLine = MKPolyline(coordinates: [source.coordinate, destination.coordinate], count: 2) polylines.append(straightLine) } } routePolylines = polylines isLoadingRoutes = false } } // MARK: - Day Section (Brutalist) struct DaySection_Brutalist: View { let dayNumber: Int let date: Date let games: [RichGame] @Environment(\.colorScheme) private var colorScheme private var textColor: Color { colorScheme == .dark ? .white : .black } private var formattedDate: String { date.formatted(.dateTime.weekday(.wide).month().day()).uppercased() } var body: some View { VStack(alignment: .leading, spacing: 12) { // Day header HStack { Text("DAY \(dayNumber)") .font(.system(.title2, design: .monospaced).bold()) .foregroundStyle(textColor) Spacer() Text("\(games.count) GAME\(games.count > 1 ? "S" : "")") .font(.system(.caption, design: .monospaced)) .foregroundStyle(Theme.warmOrange) } Text(formattedDate) .font(.system(.caption, design: .monospaced)) .foregroundStyle(textColor.opacity(0.5)) .tracking(1) // City if let city = games.first?.stadium.city { HStack(spacing: 4) { Image(systemName: "mappin") .font(.caption2) Text(city.uppercased()) .font(.system(.caption, design: .monospaced)) } .foregroundStyle(textColor.opacity(0.6)) } // Games ForEach(games, id: \.game.id) { richGame in GameRow_Brutalist(game: richGame) } } .padding(16) .border(textColor.opacity(0.2), width: 1) } } // MARK: - Game Row (Brutalist) struct GameRow_Brutalist: View { let game: RichGame @Environment(\.colorScheme) private var colorScheme private var textColor: Color { colorScheme == .dark ? .white : .black } var body: some View { HStack(spacing: 12) { // Sport indicator - vertical bar Rectangle() .fill(game.game.sport.themeColor) .frame(width: 4) VStack(alignment: .leading, spacing: 4) { // Matchup HStack(spacing: 8) { Text(game.awayTeam.abbreviation) .font(.system(.body, design: .monospaced).bold()) Text("@") .font(.system(.caption, design: .monospaced)) .foregroundStyle(textColor.opacity(0.5)) Text(game.homeTeam.abbreviation) .font(.system(.body, design: .monospaced).bold()) } .foregroundStyle(textColor) // Stadium Text(game.stadium.name.uppercased()) .font(.system(.caption2, design: .monospaced)) .foregroundStyle(textColor.opacity(0.5)) } Spacer() // Time Text(game.localGameTimeShort) .font(.system(.subheadline, design: .monospaced).bold()) .foregroundStyle(Theme.warmOrange) } .padding(12) .background(textColor.opacity(0.03)) .border(textColor.opacity(0.1), width: 1) } } // MARK: - Travel Section (Brutalist) struct TravelSection_Brutalist: View { let segment: TravelSegment @Environment(\.colorScheme) private var colorScheme private var textColor: Color { colorScheme == .dark ? .white : .black } var body: some View { VStack(spacing: 0) { // Connector Rectangle() .fill(Theme.routeGold) .frame(width: 2, height: 20) // Travel card HStack(spacing: 12) { Image(systemName: "car.fill") .foregroundStyle(Theme.routeGold) VStack(alignment: .leading, spacing: 2) { Text("[ TRAVEL ]") .font(.system(.caption2, design: .monospaced)) .foregroundStyle(textColor.opacity(0.4)) Text("\(segment.fromLocation.name.uppercased()) → \(segment.toLocation.name.uppercased())") .font(.system(.caption, design: .monospaced)) .foregroundStyle(textColor) } Spacer() VStack(alignment: .trailing, spacing: 2) { Text(segment.formattedDistance.uppercased()) .font(.system(.caption, design: .monospaced).bold()) Text(segment.formattedDuration.uppercased()) .font(.system(.caption2, design: .monospaced)) .foregroundStyle(textColor.opacity(0.5)) } .foregroundStyle(textColor) } .padding(12) .border(Theme.routeGold.opacity(0.3), width: 1) // Connector Rectangle() .fill(Theme.routeGold) .frame(width: 2, height: 20) } } } ``` ### Step 5: Update original TripDetailView to be a typealias Replace `TripDetailView.swift` with a simple redirect: ```swift // // TripDetailView.swift // SportsTime // // Redirects to AdaptiveTripDetailView for backward compatibility. // import SwiftUI typealias TripDetailView = AdaptiveTripDetailView ``` ### Step 6: Verify the build Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build 2>&1 | tail -30` Expected: BUILD SUCCEEDED ### Step 7: Commit ```bash git add SportsTime/Features/Trip/Views/ git commit -m "$(cat <<'EOF' feat(trip): add brutalist variant for TripDetailView - Create AdaptiveTripDetailView router - Add TripDetailView_Classic (extracted from original) - Add TripDetailView_Brutalist with monospace fonts, borders, perforated dividers - Update TripDetailView as typealias for backward compatibility Co-Authored-By: Claude Opus 4.5 EOF )" ``` --- ## Task 3: Create SavedTripsListView Variants **Files:** - Create: `SportsTime/Features/Home/Views/Variants/Classic/SavedTripsListView_Classic.swift` - Create: `SportsTime/Features/Home/Views/Variants/Brutalist/SavedTripsListView_Brutalist.swift` - Create: `SportsTime/Features/Home/Views/AdaptiveSavedTripsListView.swift` - Modify: `SportsTime/Features/Home/Views/HomeView.swift` (extract SavedTripsListView, update to use Adaptive) ### Step 1: Create directory structure ```bash mkdir -p SportsTime/Features/Home/Views/Variants/Classic mkdir -p SportsTime/Features/Home/Views/Variants/Brutalist ``` ### Step 2: Extract SavedTripsListView to Classic variant Move `SavedTripsListView` (lines 406-581 from HomeView.swift) to `Variants/Classic/SavedTripsListView_Classic.swift`, renaming to `SavedTripsListView_Classic`. ### Step 3: Create SavedTripsListView_Brutalist Create `SportsTime/Features/Home/Views/Variants/Brutalist/SavedTripsListView_Brutalist.swift` with brutalist styling. ### Step 4: Create AdaptiveSavedTripsListView router ### Step 5: Update HomeView.swift Replace inline `SavedTripsListView` with `AdaptiveSavedTripsListView`. ### Step 6: Verify build and commit --- ## Task 4: Create ScheduleListView Variants **Files:** - Create: `SportsTime/Features/Schedule/Views/Variants/Classic/ScheduleListView_Classic.swift` - Create: `SportsTime/Features/Schedule/Views/Variants/Brutalist/ScheduleListView_Brutalist.swift` - Create: `SportsTime/Features/Schedule/Views/AdaptiveScheduleListView.swift` - Modify: `SportsTime/Features/Schedule/Views/ScheduleListView.swift` (typealias) - Modify: `SportsTime/Features/Home/Views/HomeView.swift` (update Schedule tab) ### Step 1: Create directory structure ```bash mkdir -p SportsTime/Features/Schedule/Views/Variants/Classic mkdir -p SportsTime/Features/Schedule/Views/Variants/Brutalist ``` ### Step 2-6: Same pattern as TripDetailView --- ## Task 5: Create SettingsView Variants **Files:** - Create: `SportsTime/Features/Settings/Views/Variants/Classic/SettingsView_Classic.swift` - Create: `SportsTime/Features/Settings/Views/Variants/Brutalist/SettingsView_Brutalist.swift` - Create: `SportsTime/Features/Settings/Views/AdaptiveSettingsView.swift` - Modify: `SportsTime/Features/Settings/Views/SettingsView.swift` (typealias) - Modify: `SportsTime/Features/Home/Views/HomeView.swift` (update Settings tab) ### Step 1: Create directory structure ```bash mkdir -p SportsTime/Features/Settings/Views/Variants/Classic mkdir -p SportsTime/Features/Settings/Views/Variants/Brutalist ``` ### Step 2-6: Same pattern as TripDetailView --- ## Task 6: Run Tests and Fix Issues **Step 1: Run full test suite** Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test` Expected: All tests pass **Step 2: Fix any failing tests** Update test files that reference view types directly. **Step 3: Commit fixes** --- ## Task 7: Final Verification and Documentation Update **Step 1: Test all design styles in simulator** 1. Build and run app 2. Go to Settings → Home Screen Style 3. Select "Classic" - verify all screens look correct 4. Select "Brutalist" - verify all screens have brutalist styling **Step 2: Update CLAUDE.md if needed** Add note about adaptive view pattern if helpful for future development. **Step 3: Final commit** ```bash git add -A git commit -m "$(cat <<'EOF' docs: complete brutalist app-wide implementation All main screens now support Classic and Brutalist design variants: - TripDetailView - SavedTripsListView - ScheduleListView - SettingsView Other 22 design styles fall back to Classic for non-home screens. Co-Authored-By: Claude Opus 4.5 EOF )" ```