# Loading System Redesign Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Replace all loading indicators with a unified, Apple-style design system using three components: LoadingSpinner, LoadingPlaceholder, and LoadingSheet.

**Architecture:** Create new Loading/ folder with three focused components. Deprecate 6 existing components in AnimatedComponents.swift. Replace 5 native ProgressView() usages. Refactor LoadingTripsView to use new skeletons.

**Tech Stack:** SwiftUI, Swift Testing

---

## Task 1: Create LoadingSpinner Component

**Files:**
- Create: `SportsTime/Core/Theme/Loading/LoadingSpinner.swift`
- Test: `SportsTimeTests/Loading/LoadingSpinnerTests.swift`

**Step 1: Create the Loading directory**

```bash
mkdir -p SportsTime/Core/Theme/Loading
mkdir -p SportsTimeTests/Loading
```

**Step 2: Write the failing test**

Create `SportsTimeTests/Loading/LoadingSpinnerTests.swift`:

```swift
//
//  LoadingSpinnerTests.swift
//  SportsTimeTests
//

import Testing
import SwiftUI
@testable import SportsTime

struct LoadingSpinnerTests {

    @Test func smallSizeHasCorrectDimensions() {
        let config = LoadingSpinner.Size.small
        #expect(config.diameter == 16)
        #expect(config.strokeWidth == 2)
    }

    @Test func mediumSizeHasCorrectDimensions() {
        let config = LoadingSpinner.Size.medium
        #expect(config.diameter == 24)
        #expect(config.strokeWidth == 3)
    }

    @Test func largeSizeHasCorrectDimensions() {
        let config = LoadingSpinner.Size.large
        #expect(config.diameter == 40)
        #expect(config.strokeWidth == 4)
    }

    @Test func spinnerCanBeCreatedWithAllSizes() {
        let small = LoadingSpinner(size: .small)
        let medium = LoadingSpinner(size: .medium)
        let large = LoadingSpinner(size: .large)

        #expect(small.size == .small)
        #expect(medium.size == .medium)
        #expect(large.size == .large)
    }

    @Test func spinnerCanHaveOptionalLabel() {
        let withLabel = LoadingSpinner(size: .medium, label: "Loading...")
        let withoutLabel = LoadingSpinner(size: .medium)

        #expect(withLabel.label == "Loading...")
        #expect(withoutLabel.label == nil)
    }
}
```

**Step 3: Run test to verify it fails**

Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/LoadingSpinnerTests test`

Expected: FAIL with "Cannot find 'LoadingSpinner' in scope"

**Step 4: Write the LoadingSpinner implementation**

Create `SportsTime/Core/Theme/Loading/LoadingSpinner.swift`:

```swift
//
//  LoadingSpinner.swift
//  SportsTime
//
//  Apple-style indeterminate spinner with theme-aware colors.
//

import SwiftUI

struct LoadingSpinner: View {
    enum Size {
        case small, medium, large

        var diameter: CGFloat {
            switch self {
            case .small: return 16
            case .medium: return 24
            case .large: return 40
            }
        }

        var strokeWidth: CGFloat {
            switch self {
            case .small: return 2
            case .medium: return 3
            case .large: return 4
            }
        }

        var labelFont: Font {
            switch self {
            case .small, .medium: return .subheadline
            case .large: return .body
            }
        }
    }

    let size: Size
    let label: String?

    @State private var rotation: Double = 0
    @Environment(\.colorScheme) private var colorScheme

    init(size: Size = .medium, label: String? = nil) {
        self.size = size
        self.label = label
    }

    var body: some View {
        HStack(spacing: Theme.Spacing.sm) {
            spinnerView

            if let label {
                Text(label)
                    .font(size.labelFont)
                    .foregroundStyle(Theme.textSecondary(colorScheme))
            }
        }
    }

    private var spinnerView: some View {
        ZStack {
            // Background track
            Circle()
                .stroke(Theme.warmOrange.opacity(0.15), lineWidth: size.strokeWidth)

            // Rotating arc (270 degrees)
            Circle()
                .trim(from: 0, to: 0.75)
                .stroke(Theme.warmOrange, style: StrokeStyle(lineWidth: size.strokeWidth, lineCap: .round))
                .rotationEffect(.degrees(rotation))
        }
        .frame(width: size.diameter, height: size.diameter)
        .onAppear {
            withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)) {
                rotation = 360
            }
        }
    }
}

// MARK: - Preview

#Preview("Loading Spinner Sizes") {
    VStack(spacing: 40) {
        LoadingSpinner(size: .small, label: "Loading...")
        LoadingSpinner(size: .medium, label: "Loading games...")
        LoadingSpinner(size: .large, label: "Planning trip")
        LoadingSpinner(size: .medium)
    }
    .padding(40)
    .themedBackground()
}
```

**Step 5: Run tests to verify they pass**

Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/LoadingSpinnerTests test`

Expected: All 5 tests PASS

**Step 6: Commit**

```bash
git add SportsTime/Core/Theme/Loading/LoadingSpinner.swift SportsTimeTests/Loading/LoadingSpinnerTests.swift
git commit -m "$(cat <<'EOF'
feat: add LoadingSpinner component

Apple-style indeterminate spinner with three sizes (small/medium/large),
optional label, and theme-aware colors. Uses 270-degree arc with
1-second rotation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Task 2: Create LoadingPlaceholder Component

**Files:**
- Create: `SportsTime/Core/Theme/Loading/LoadingPlaceholder.swift`
- Test: `SportsTimeTests/Loading/LoadingPlaceholderTests.swift`

**Step 1: Write the failing test**

Create `SportsTimeTests/Loading/LoadingPlaceholderTests.swift`:

```swift
//
//  LoadingPlaceholderTests.swift
//  SportsTimeTests
//

import Testing
import SwiftUI
@testable import SportsTime

struct LoadingPlaceholderTests {

    @Test func rectangleHasCorrectDimensions() {
        let rect = LoadingPlaceholder.rectangle(width: 100, height: 20)
        #expect(rect.width == 100)
        #expect(rect.height == 20)
    }

    @Test func circleHasCorrectDiameter() {
        let circle = LoadingPlaceholder.circle(diameter: 40)
        #expect(circle.diameter == 40)
    }

    @Test func capsuleHasCorrectDimensions() {
        let capsule = LoadingPlaceholder.capsule(width: 80, height: 24)
        #expect(capsule.width == 80)
        #expect(capsule.height == 24)
    }

    @Test func animationCycleDurationIsCorrect() {
        #expect(LoadingPlaceholder.animationDuration == 1.2)
    }

    @Test func opacityRangeIsSubtle() {
        #expect(LoadingPlaceholder.minOpacity == 0.3)
        #expect(LoadingPlaceholder.maxOpacity == 0.5)
    }
}
```

**Step 2: Run test to verify it fails**

Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/LoadingPlaceholderTests test`

Expected: FAIL with "Cannot find 'LoadingPlaceholder' in scope"

**Step 3: Write the LoadingPlaceholder implementation**

Create `SportsTime/Core/Theme/Loading/LoadingPlaceholder.swift`:

```swift
//
//  LoadingPlaceholder.swift
//  SportsTime
//
//  Skeleton placeholder shapes with gentle opacity pulse animation.
//

import SwiftUI

enum LoadingPlaceholder {
    static let animationDuration: Double = 1.2
    static let minOpacity: Double = 0.3
    static let maxOpacity: Double = 0.5

    static func rectangle(width: CGFloat, height: CGFloat) -> PlaceholderRectangle {
        PlaceholderRectangle(width: width, height: height)
    }

    static func circle(diameter: CGFloat) -> PlaceholderCircle {
        PlaceholderCircle(diameter: diameter)
    }

    static func capsule(width: CGFloat, height: CGFloat) -> PlaceholderCapsule {
        PlaceholderCapsule(width: width, height: height)
    }

    static var card: PlaceholderCard {
        PlaceholderCard()
    }

    static var listRow: PlaceholderListRow {
        PlaceholderListRow()
    }
}

// MARK: - Placeholder Rectangle

struct PlaceholderRectangle: View {
    let width: CGFloat
    let height: CGFloat

    @State private var isAnimating = false
    @Environment(\.colorScheme) private var colorScheme

    var body: some View {
        RoundedRectangle(cornerRadius: 4)
            .fill(placeholderColor)
            .frame(width: width, height: height)
            .opacity(isAnimating ? LoadingPlaceholder.maxOpacity : LoadingPlaceholder.minOpacity)
            .onAppear {
                withAnimation(.easeInOut(duration: LoadingPlaceholder.animationDuration).repeatForever(autoreverses: true)) {
                    isAnimating = true
                }
            }
    }

    private var placeholderColor: Color {
        colorScheme == .dark ? Theme.cardBackgroundElevated(colorScheme) : Theme.textMuted(colorScheme)
    }
}

// MARK: - Placeholder Circle

struct PlaceholderCircle: View {
    let diameter: CGFloat

    @State private var isAnimating = false
    @Environment(\.colorScheme) private var colorScheme

    var body: some View {
        Circle()
            .fill(placeholderColor)
            .frame(width: diameter, height: diameter)
            .opacity(isAnimating ? LoadingPlaceholder.maxOpacity : LoadingPlaceholder.minOpacity)
            .onAppear {
                withAnimation(.easeInOut(duration: LoadingPlaceholder.animationDuration).repeatForever(autoreverses: true)) {
                    isAnimating = true
                }
            }
    }

    private var placeholderColor: Color {
        colorScheme == .dark ? Theme.cardBackgroundElevated(colorScheme) : Theme.textMuted(colorScheme)
    }
}

// MARK: - Placeholder Capsule

struct PlaceholderCapsule: View {
    let width: CGFloat
    let height: CGFloat

    @State private var isAnimating = false
    @Environment(\.colorScheme) private var colorScheme

    var body: some View {
        Capsule()
            .fill(placeholderColor)
            .frame(width: width, height: height)
            .opacity(isAnimating ? LoadingPlaceholder.maxOpacity : LoadingPlaceholder.minOpacity)
            .onAppear {
                withAnimation(.easeInOut(duration: LoadingPlaceholder.animationDuration).repeatForever(autoreverses: true)) {
                    isAnimating = true
                }
            }
    }

    private var placeholderColor: Color {
        colorScheme == .dark ? Theme.cardBackgroundElevated(colorScheme) : Theme.textMuted(colorScheme)
    }
}

// MARK: - Placeholder Card (Trip Card Skeleton)

struct PlaceholderCard: View {
    @State private var isAnimating = false
    @Environment(\.colorScheme) private var colorScheme

    var body: some View {
        VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
            // Header row
            HStack {
                placeholderRect(width: 60, height: 20)
                Spacer()
                placeholderRect(width: 40, height: 16)
            }

            // Title and subtitle
            VStack(alignment: .leading, spacing: 4) {
                placeholderRect(width: 120, height: 16)
                placeholderRect(width: 160, height: 14)
            }

            // Stats row
            HStack(spacing: Theme.Spacing.sm) {
                placeholderRect(width: 70, height: 14)
                placeholderRect(width: 60, height: 14)
            }
        }
        .padding(Theme.Spacing.md)
        .frame(width: 200)
        .background(Theme.cardBackground(colorScheme))
        .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large))
        .overlay {
            RoundedRectangle(cornerRadius: Theme.CornerRadius.large)
                .stroke(Theme.surfaceGlow(colorScheme), lineWidth: 1)
        }
        .onAppear {
            withAnimation(.easeInOut(duration: LoadingPlaceholder.animationDuration).repeatForever(autoreverses: true)) {
                isAnimating = true
            }
        }
    }

    private func placeholderRect(width: CGFloat, height: CGFloat) -> some View {
        RoundedRectangle(cornerRadius: 4)
            .fill(placeholderColor)
            .frame(width: width, height: height)
            .opacity(isAnimating ? LoadingPlaceholder.maxOpacity : LoadingPlaceholder.minOpacity)
    }

    private var placeholderColor: Color {
        colorScheme == .dark ? Theme.cardBackgroundElevated(colorScheme) : Theme.textMuted(colorScheme)
    }
}

// MARK: - Placeholder List Row

struct PlaceholderListRow: View {
    @State private var isAnimating = false
    @Environment(\.colorScheme) private var colorScheme

    var body: some View {
        HStack(spacing: Theme.Spacing.md) {
            placeholderCircle(diameter: 40)

            VStack(alignment: .leading, spacing: 6) {
                placeholderRect(width: 140, height: 16)
                placeholderRect(width: 100, height: 12)
            }

            Spacer()

            placeholderRect(width: 50, height: 14)
        }
        .padding(Theme.Spacing.md)
        .onAppear {
            withAnimation(.easeInOut(duration: LoadingPlaceholder.animationDuration).repeatForever(autoreverses: true)) {
                isAnimating = true
            }
        }
    }

    private func placeholderRect(width: CGFloat, height: CGFloat) -> some View {
        RoundedRectangle(cornerRadius: 4)
            .fill(placeholderColor)
            .frame(width: width, height: height)
            .opacity(isAnimating ? LoadingPlaceholder.maxOpacity : LoadingPlaceholder.minOpacity)
    }

    private func placeholderCircle(diameter: CGFloat) -> some View {
        Circle()
            .fill(placeholderColor)
            .frame(width: diameter, height: diameter)
            .opacity(isAnimating ? LoadingPlaceholder.maxOpacity : LoadingPlaceholder.minOpacity)
    }

    private var placeholderColor: Color {
        colorScheme == .dark ? Theme.cardBackgroundElevated(colorScheme) : Theme.textMuted(colorScheme)
    }
}

// MARK: - Preview

#Preview("Loading Placeholders") {
    VStack(spacing: 30) {
        HStack(spacing: 20) {
            LoadingPlaceholder.rectangle(width: 100, height: 20)
            LoadingPlaceholder.circle(diameter: 40)
            LoadingPlaceholder.capsule(width: 80, height: 24)
        }

        LoadingPlaceholder.card

        LoadingPlaceholder.listRow
            .background(Theme.cardBackground(.dark))
    }
    .padding(20)
    .themedBackground()
}
```

**Step 4: Run tests to verify they pass**

Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/LoadingPlaceholderTests test`

Expected: All 5 tests PASS

**Step 5: Commit**

```bash
git add SportsTime/Core/Theme/Loading/LoadingPlaceholder.swift SportsTimeTests/Loading/LoadingPlaceholderTests.swift
git commit -m "$(cat <<'EOF'
feat: add LoadingPlaceholder component

Skeleton placeholder shapes with gentle opacity pulse animation (0.3-0.5
over 1.2s). Includes rectangle, circle, capsule primitives plus card and
listRow composites. Theme-aware colors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Task 3: Create LoadingSheet Component

**Files:**
- Create: `SportsTime/Core/Theme/Loading/LoadingSheet.swift`
- Test: `SportsTimeTests/Loading/LoadingSheetTests.swift`

**Step 1: Write the failing test**

Create `SportsTimeTests/Loading/LoadingSheetTests.swift`:

```swift
//
//  LoadingSheetTests.swift
//  SportsTimeTests
//

import Testing
import SwiftUI
@testable import SportsTime

struct LoadingSheetTests {

    @Test func sheetRequiresLabel() {
        let sheet = LoadingSheet(label: "Planning trip")
        #expect(sheet.label == "Planning trip")
    }

    @Test func sheetCanHaveOptionalDetail() {
        let withDetail = LoadingSheet(label: "Exporting", detail: "Generating maps...")
        let withoutDetail = LoadingSheet(label: "Loading")

        #expect(withDetail.detail == "Generating maps...")
        #expect(withoutDetail.detail == nil)
    }

    @Test func backgroundOpacityIsCorrect() {
        #expect(LoadingSheet.backgroundOpacity == 0.5)
    }
}
```

**Step 2: Run test to verify it fails**

Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/LoadingSheetTests test`

Expected: FAIL with "Cannot find 'LoadingSheet' in scope"

**Step 3: Write the LoadingSheet implementation**

Create `SportsTime/Core/Theme/Loading/LoadingSheet.swift`:

```swift
//
//  LoadingSheet.swift
//  SportsTime
//
//  Full-screen blocking loading overlay with centered card.
//

import SwiftUI

struct LoadingSheet: View {
    static let backgroundOpacity: Double = 0.5

    let label: String
    let detail: String?

    @Environment(\.colorScheme) private var colorScheme

    init(label: String, detail: String? = nil) {
        self.label = label
        self.detail = detail
    }

    var body: some View {
        ZStack {
            // Dimmed background
            Color.black.opacity(Self.backgroundOpacity)
                .ignoresSafeArea()

            // Centered card
            VStack(spacing: Theme.Spacing.lg) {
                LoadingSpinner(size: .large)

                VStack(spacing: Theme.Spacing.xs) {
                    Text(label)
                        .font(.headline)
                        .foregroundStyle(Theme.textPrimary(colorScheme))

                    if let detail {
                        Text(detail)
                            .font(.subheadline)
                            .foregroundStyle(Theme.textSecondary(colorScheme))
                            .multilineTextAlignment(.center)
                    }
                }
            }
            .padding(Theme.Spacing.xl)
            .background(Theme.cardBackground(colorScheme))
            .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large))
            .shadow(color: .black.opacity(0.2), radius: 16, y: 8)
        }
        .transition(.opacity)
    }
}

// MARK: - Preview

#Preview("Loading Sheet") {
    ZStack {
        Color.gray.ignoresSafeArea()
        LoadingSheet(label: "Planning trip")
    }
}

#Preview("Loading Sheet with Detail") {
    ZStack {
        Color.gray.ignoresSafeArea()
        LoadingSheet(label: "Exporting PDF", detail: "Generating maps...")
    }
}
```

**Step 4: Run tests to verify they pass**

Run: `xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/LoadingSheetTests test`

Expected: All 3 tests PASS

**Step 5: Commit**

```bash
git add SportsTime/Core/Theme/Loading/LoadingSheet.swift SportsTimeTests/Loading/LoadingSheetTests.swift
git commit -m "$(cat <<'EOF'
feat: add LoadingSheet component

Full-screen blocking overlay with dimmed background (0.5 opacity),
centered card with large spinner, label, and optional detail text.
Replaces LoadingOverlay and PlanningProgressView.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Task 4: Replace ProgressView in SportsStep

**Files:**
- Modify: `SportsTime/Features/Trip/Views/Wizard/Steps/SportsStep.swift:30-38`

**Step 1: Replace ProgressView with LoadingSpinner**

In `SportsStep.swift`, replace lines 30-38:

```swift
// OLD (lines 30-38):
            if isLoading {
                HStack {
                    ProgressView()
                        .scaleEffect(0.8)
                    Text("Checking game availability...")
                        .font(.caption)
                        .foregroundStyle(Theme.textMuted(colorScheme))
                }
                .padding(.vertical, Theme.Spacing.sm)
            }
```

With:

```swift
            if isLoading {
                LoadingSpinner(size: .small, label: "Checking availability...")
                    .padding(.vertical, Theme.Spacing.sm)
            }
```

**Step 2: Build to verify**

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

Expected: Build succeeds

**Step 3: Commit**

```bash
git add SportsTime/Features/Trip/Views/Wizard/Steps/SportsStep.swift
git commit -m "$(cat <<'EOF'
refactor: replace ProgressView with LoadingSpinner in SportsStep

Use new LoadingSpinner(size: .small) for consistent loading UI.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Task 5: Replace ProgressView in ReviewStep

**Files:**
- Modify: `SportsTime/Features/Trip/Views/Wizard/Steps/ReviewStep.swift:48-53`

**Step 1: Replace ProgressView with LoadingSpinner**

In `ReviewStep.swift`, replace the button content (lines 48-56):

```swift
// OLD:
            Button(action: onPlan) {
                HStack {
                    if isPlanning {
                        ProgressView()
                            .scaleEffect(0.8)
                            .tint(.white)
                    }
                    Text(isPlanning ? "Planning..." : "Plan My Trip")
                        .fontWeight(.semibold)
                }
```

With:

```swift
            Button(action: onPlan) {
                HStack(spacing: Theme.Spacing.sm) {
                    if isPlanning {
                        LoadingSpinner(size: .small)
                            .colorScheme(.dark)  // Force white on orange button
                    }
                    Text(isPlanning ? "Planning..." : "Plan My Trip")
                        .fontWeight(.semibold)
                }
```

**Step 2: Build to verify**

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

Expected: Build succeeds

**Step 3: Commit**

```bash
git add SportsTime/Features/Trip/Views/Wizard/Steps/ReviewStep.swift
git commit -m "$(cat <<'EOF'
refactor: replace ProgressView with LoadingSpinner in ReviewStep

Use LoadingSpinner(size: .small) in Plan My Trip button.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Task 6: Replace ProgressView in StadiumVisitHistoryView

**Files:**
- Modify: `SportsTime/Features/Progress/Views/StadiumVisitHistoryView.swift:16-17`

**Step 1: Replace ProgressView with LoadingSpinner**

In `StadiumVisitHistoryView.swift`, replace line 17:

```swift
// OLD:
                if isLoading {
                    ProgressView()
```

With:

```swift
                if isLoading {
                    LoadingSpinner(size: .medium)
```

**Step 2: Build to verify**

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

Expected: Build succeeds

**Step 3: Commit**

```bash
git add SportsTime/Features/Progress/Views/StadiumVisitHistoryView.swift
git commit -m "$(cat <<'EOF'
refactor: replace ProgressView with LoadingSpinner in StadiumVisitHistoryView

Use LoadingSpinner(size: .medium) for consistent loading UI.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Task 7: Replace ProgressView in GamesHistoryView

**Files:**
- Modify: `SportsTime/Features/Progress/Views/GamesHistoryView.swift:17`

**Step 1: Replace ProgressView with LoadingSpinner**

In `GamesHistoryView.swift`, replace line 17:

```swift
// OLD:
                ProgressView("Loading games...")
```

With:

```swift
                LoadingSpinner(size: .medium, label: "Loading games...")
```

**Step 2: Build to verify**

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

Expected: Build succeeds

**Step 3: Commit**

```bash
git add SportsTime/Features/Progress/Views/GamesHistoryView.swift
git commit -m "$(cat <<'EOF'
refactor: replace ProgressView with LoadingSpinner in GamesHistoryView

Use LoadingSpinner with label for consistent loading UI.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Task 8: Replace ProgressView in StadiumVisitSheet

**Files:**
- Modify: `SportsTime/Features/Progress/Views/StadiumVisitSheet.swift:234-236`

**Step 1: Replace ProgressView with LoadingSpinner**

In `StadiumVisitSheet.swift`, replace lines 234-236:

```swift
// OLD:
                            HStack {
                                    if isLookingUpGame {
                                        ProgressView()
                                            .scaleEffect(0.8)
                                    } else {
```

With:

```swift
                            HStack {
                                    if isLookingUpGame {
                                        LoadingSpinner(size: .small)
                                    } else {
```

**Step 2: Build to verify**

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

Expected: Build succeeds

**Step 3: Commit**

```bash
git add SportsTime/Features/Progress/Views/StadiumVisitSheet.swift
git commit -m "$(cat <<'EOF'
refactor: replace ProgressView with LoadingSpinner in StadiumVisitSheet

Use LoadingSpinner(size: .small) in Look Up Game button.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Task 9: Replace planningOverlay in TripCreationView

**Files:**
- Modify: `SportsTime/Features/Trip/Views/TripCreationView.swift:867-876`

**Step 1: Replace PlanningProgressView with LoadingSheet**

In `TripCreationView.swift`, replace lines 867-876:

```swift
// OLD:
    private var planningOverlay: some View {
        ZStack {
            Color.black.opacity(0.5)
                .ignoresSafeArea()

            PlanningProgressView()
                .padding(40)
                .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 24))
        }
    }
```

With:

```swift
    private var planningOverlay: some View {
        LoadingSheet(label: "Planning trip")
    }
```

**Step 2: Build to verify**

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

Expected: Build succeeds

**Step 3: Commit**

```bash
git add SportsTime/Features/Trip/Views/TripCreationView.swift
git commit -m "$(cat <<'EOF'
refactor: replace PlanningProgressView with LoadingSheet

Use new LoadingSheet component for planning overlay.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Task 10: Refactor LoadingTripsView to Use New Placeholders

**Files:**
- Modify: `SportsTime/Features/Home/Views/LoadingTripsView.swift`

**Step 1: Rewrite LoadingTripsView to use LoadingPlaceholder**

Replace the entire file with:

```swift
//
//  LoadingTripsView.swift
//  SportsTime
//
//  Loading state for suggested trips carousel using skeleton placeholders.
//

import SwiftUI

struct LoadingTripsView: View {
    let message: String
    @Environment(\.colorScheme) private var colorScheme

    var body: some View {
        VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
            // Header
            HStack {
                Text("Featured Trips")
                    .font(.title2)
                    .foregroundStyle(Theme.textPrimary(colorScheme))
                Spacer()
            }

            // Loading indicator with message
            LoadingSpinner(size: .small, label: message)
                .padding(.vertical, Theme.Spacing.xs)

            // Placeholder cards
            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: Theme.Spacing.md) {
                    ForEach(0..<3, id: \.self) { _ in
                        LoadingPlaceholder.card
                    }
                }
            }
        }
    }
}

#Preview {
    VStack {
        LoadingTripsView(message: "Loading trips...")
            .padding()
    }
    .themedBackground()
}
```

**Step 2: Build to verify**

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

Expected: Build succeeds

**Step 3: Commit**

```bash
git add SportsTime/Features/Home/Views/LoadingTripsView.swift
git commit -m "$(cat <<'EOF'
refactor: simplify LoadingTripsView with new loading components

Use LoadingSpinner and LoadingPlaceholder.card instead of custom
LoadingDots and PlaceholderCard with shimmer animation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Task 11: Remove Deprecated Components from AnimatedComponents.swift

**Files:**
- Modify: `SportsTime/Core/Theme/AnimatedComponents.swift`

**Step 1: Remove deprecated loading components**

Remove the following from `AnimatedComponents.swift`:
- `ThemedSpinner` (lines 106-136)
- `ThemedSpinnerCompact` (lines 138-164)
- `PlanningProgressView` (lines 245-279)
- `LoadingOverlay` (lines 348-417)
- Preview for "Themed Spinners" (lines 419-436)
- Preview for "Loading Overlay" (lines 459-467)
- Preview for "Loading Overlay with Progress" (lines 469-479)

Keep:
- `AnimatedRouteGraphic`
- `PulsingDot`
- `RoutePreviewStrip`
- `StatPill`
- `EmptyStateView`
- Their previews

**Step 2: Build to verify**

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

Expected: Build succeeds

**Step 3: Commit**

```bash
git add SportsTime/Core/Theme/AnimatedComponents.swift
git commit -m "$(cat <<'EOF'
refactor: remove deprecated loading components from AnimatedComponents

Remove ThemedSpinner, ThemedSpinnerCompact, PlanningProgressView,
and LoadingOverlay. These are replaced by the new Loading/ components.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Task 12: Delete LoadingTextGenerator.swift

**Files:**
- Delete: `SportsTime/Core/Services/LoadingTextGenerator.swift`

**Step 1: Remove the file**

```bash
git rm SportsTime/Core/Services/LoadingTextGenerator.swift
```

**Step 2: Build to verify no references remain**

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

Expected: Build succeeds (if there are errors, search for `LoadingTextGenerator` usages and remove them)

**Step 3: Commit**

```bash
git commit -m "$(cat <<'EOF'
refactor: remove LoadingTextGenerator

Loading messages are now static context-specific labels,
no longer AI-generated or rotating.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Task 13: Delete LoadingDots and PlaceholderCard (Cleanup)

**Files:**
- Modify: `SportsTime/Features/Home/Views/LoadingTripsView.swift` (if still contains old components)

**Step 1: Verify LoadingDots and PlaceholderCard are no longer in codebase**

```bash
grep -r "LoadingDots\|PlaceholderCard" SportsTime/
```

If any usages remain, remove them.

**Step 2: Build 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 3: Final commit if needed**

```bash
git add -A
git commit -m "$(cat <<'EOF'
chore: final cleanup of deprecated loading components

Remove any remaining references to LoadingDots and PlaceholderCard.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"
```

---

## Summary

| Task | Description | Tests Added |
|------|-------------|-------------|
| 1 | Create LoadingSpinner | 5 |
| 2 | Create LoadingPlaceholder | 5 |
| 3 | Create LoadingSheet | 3 |
| 4 | Replace ProgressView in SportsStep | - |
| 5 | Replace ProgressView in ReviewStep | - |
| 6 | Replace ProgressView in StadiumVisitHistoryView | - |
| 7 | Replace ProgressView in GamesHistoryView | - |
| 8 | Replace ProgressView in StadiumVisitSheet | - |
| 9 | Replace planningOverlay in TripCreationView | - |
| 10 | Refactor LoadingTripsView | - |
| 11 | Remove deprecated components | - |
| 12 | Delete LoadingTextGenerator | - |
| 13 | Final cleanup and verification | - |

**Total new tests:** 13
**Files created:** 6 (3 components + 3 test files)
**Files modified:** 8
**Files deleted:** 1 (LoadingTextGenerator.swift)
**Components removed:** 6 (ThemedSpinner, ThemedSpinnerCompact, LoadingDots, PlaceholderCard, PlanningProgressView, LoadingOverlay)
