import SwiftUI struct AlternativeCard: View { let alternative: Alternative var body: some View { HStack(spacing: 12) { // Product Image if let imageURL = alternative.product.imageURL { AsyncImage(url: imageURL) { phase in switch phase { case .success(let image): image .resizable() .aspectRatio(contentMode: .fill) case .failure: imagePlaceholder default: ProgressView() } } .frame(width: 80, height: 80) .background(Color(.systemGray6)) .cornerRadius(12) .clipped() } else { imagePlaceholder .frame(width: 80, height: 80) } // Product Info VStack(alignment: .leading, spacing: 6) { Text(alternative.product.brand) .font(.caption) .foregroundColor(.secondary) Text(alternative.product.name) .font(.subheadline.bold()) .lineLimit(2) HStack(spacing: 12) { // Score HStack(spacing: 4) { Circle() .fill(scoreColor(alternative.product.score)) .frame(width: 10, height: 10) Text("\(alternative.product.score)") .font(.caption.bold()) } // NOVA Text("NOVA \(alternative.product.novaGroup)") .font(.caption) .foregroundColor(.secondary) // Price if let price = alternative.formattedPrice { Text(price) .font(.caption.bold()) .foregroundColor(.green) } // Distance if let distance = alternative.formattedDistance { Text(distance) .font(.caption) .foregroundColor(.secondary) } } } Spacer() // Score Ring (mini) ZStack { Circle() .stroke(lineWidth: 4) .opacity(0.2) .foregroundColor(scoreColor(alternative.product.score)) Circle() .trim(from: 0, to: Double(alternative.product.score) / 100.0) .stroke(style: StrokeStyle(lineWidth: 4, lineCap: .round)) .foregroundColor(scoreColor(alternative.product.score)) .rotationEffect(.degrees(-90)) Text("\(alternative.product.score)") .font(.caption2.bold()) } .frame(width: 40, height: 40) } .padding() .background(Color(.secondarySystemBackground)) .cornerRadius(16) } private var imagePlaceholder: some View { ZStack { Color(.systemGray5) Image(systemName: "carrot.fill") .foregroundColor(.gray) } .cornerRadius(12) } private func scoreColor(_ score: Int) -> Color { switch score { case 80...100: return .green case 50..<80: return .yellow case 30..<50: return .orange default: return .red } } }